Estoy usando el filtro svg feSpecularLighting y estoy animando la posición fePointLight donde se mueve el mouse en el escritorio.

<feSpecularLighting in="blur" surfaceScale="15" specularConstant="2.5" specularExponent="200" result="specOut" lighting-color="white">
    <fePointLight x="-10000" y="-10000" z="8000" />
</feSpecularLighting>

Parece que no puedo encontrar una manera de usar los efectos de transición de CSS para facilitar los cambios de atributos x y y, no creo que los filtros svg funcionen así. Entonces, me pregunto si es posible facilitar los cambios de enteros para x y y

Vea el ejemplo de trabajo a continuación, sin suavizar los valores de atributo x y y.

// lighting effect variables
let lightDist = 10000;
let currentLightPos = {
  x: -lightDist,
  y: -lightDist
};
let currentMousePos = {
  x: -1,
  y: -1
};
let windowWidth = $(window).outerWidth();
let windowHeight = $(window).outerHeight();
let ratio = {
  x: lightDist / (windowWidth / 2),
  y: lightDist / (windowHeight / 2)
};

// when the window is resized
$(window).on('resize', function() {

  // update lighting effect variables
  windowWidth = $(window).outerWidth();
  windowHeight = $(window).outerHeight();
  ratio = {
    x: lightDist / (windowWidth / 2),
    y: lightDist / (windowHeight / 2)
  };

});

// when the mouse is moved (desktop)
$(document).on('mousemove', function(e) {

  // get our current mouse position
  currentMousePos.x = e.pageX;
  currentMousePos.y = e.pageY;

  // if mouse is on right side of window
  if (currentMousePos.x > (windowWidth / 2)) {

    // calculate the positive light x position
    currentLightPos.x = ratio.x * (currentMousePos.x - (windowWidth / 2));

    // if mouse is on left side of window
  } else if (currentMousePos.x < (windowWidth / 2)) {

    // calculate the negative light x position
    currentLightPos.x = -lightDist + (ratio.x * currentMousePos.x);

  }

  // calculate the negative light y position
  // if mouse is on bottom side of window
  if (currentMousePos.y > (windowHeight / 2)) {

    // calculate the positive light y position
    currentLightPos.y = ratio.y * (currentMousePos.y - (windowHeight / 2));

    // if mouse is on top side of window
  } else if (currentMousePos.y < (windowHeight / 2)) {

    // calculate the negative light y position
    currentLightPos.y = -lightDist + (ratio.y * currentMousePos.y);

  }
  
  // console.log(currentLightPos.x, currentLightPos.y);

  // update shine filter fePointLight attributes
  $('fePointLight').attr({
    'x': currentLightPos.x,
    'y': currentLightPos.y
  });

});
.circle {
  width: 100px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<svg class="circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="50" filter="url(#shine)" />
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
    <filter id="shine" filterUnits="objectBoundingBox" x="-10%" y="-10%" width="150%" height="150%">
        <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur" />
        <feSpecularLighting in="blur" surfaceScale="15" specularConstant="2.5" specularExponent="200" result="specOut" lighting-color="white">
            <fePointLight x="-10000" y="-10000" z="8000" />
        </feSpecularLighting>
        <feComposite in="specOut" in2="SourceAlpha" operator="in" result="specOut2" />
        <feComposite in="SourceGraphic" in2="specOut2" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" />
    </filter>
</svg>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Para emular el problema que estoy teniendo, si mueve el mouse fuera de la ventana de ejemplo y vuelve a colocarlo en la ventana en una posición diferente, notará que la iluminación del punto salta instantáneamente a la posición recién calculada.

Me pregunto si es posible aliviar esto, por lo que los enteros x y y siempre se colocan en su posición recién calculada, en lugar de saltar instantáneamente a una nueva posición si coloca el mouse en la ventana desde un lado diferente?

1
joshmoto 27 jul. 2020 a las 20:13

1 respuesta

La mejor respuesta

(Aparte: mientras que su pregunta dice "números enteros", los atributos <fePointLight> también toman números reales. Por lo tanto, eso no es detrimento de la suavidad fluida).

Puede hacer esto con una animación SMIL declarativa en dos pasos: primero, declara dos <animate> elementos para los dos atributos x y y de <fePointLight>. restart="always" se asegura de que esta animación pueda reiniciarse en cualquier momento, incluso si la animación se está ejecutando actualmente. fill="freeze" conserva el valor después del final de la animación.

Pero el truco principal es configurar begin="indefinite". Esto pospone el inicio de la animación hasta que se activa desde una llamada a la API de JavaScript.

<fePointLight x="-10000" y="-10000" z="8000">
    <animate id="anim_x" attributeName="x"
             begin="indefinite" dur="0.5s" restart="always"
             from="-10000" to="-10000" fill="freeze" />
    <animate id="anim_y" attributeName="y"
             begin="indefinite" dur="0.5s" restart="always"
             from="-10000" to="-10000" fill="freeze" />
</fePointLight>

Dejé la función de aceleración en su valor predeterminado linear. Para un comportamiento más complejo, la sintaxis es un poco más complicada que las funciones de suavizado CSS. Algo comparable a ease-in-out se vería así:

    <animate id="anim_x" attributeName="x"
             begin="indefinite" dur="0.5s" restart="always"
             calcMode="spline" values="-10000;-10000" keyTimes="0;1"
             keySplines=".5 0 .5 1" fill="freeze" />

Ahora, en cada movimiento del mouse, su oyente de eventos debe hacer tres cosas:

  • Establezca el atributo <animate from> en el valor animado actual de la propiedad <fePointLight> element.x.animVal / element.y.animVal. Esto hace que la animación comience desde donde se encuentre el valor actualmente, incluso durante una animación en ejecución.
  • Establezca el atributo <animate to> en el valor currentLightPos para mover la animación hacia su objetivo.
  • inicie la animación con el método <fePointLight> element.beginElement(), cancelando cualquier animación que se esté ejecutando actualmente.

(Si estuviera usando una spline, tendría que establecer el atributo values en lugar de from y to).

  const pointLight = $('fePointLight');
  const lightX = $('#anim_x');
  const lightY = $('#anim_y');

  lightX.attr({
    from: pointLight.prop('x').animVal,
    to: currentLightPos.x
  });
  lightX[0].beginElement();
  lightY.attr({
    from: pointLight.prop('y').animVal,
    to: currentLightPos.y
  });
  lightY[0].beginElement();

Esto permite que la luz puntual "persiga" la posición actual del mouse, hasta que el movimiento del mouse se detenga y la animación tenga la oportunidad de ejecutarse hasta el final.

// lighting effect variables
let lightDist = 10000;
let currentLightPos = {
  x: -lightDist,
  y: -lightDist
};
let currentMousePos = {
  x: -1,
  y: -1
};
let windowWidth = $(window).outerWidth();
let windowHeight = $(window).outerHeight();
let ratio = {
  x: lightDist / (windowWidth / 2),
  y: lightDist / (windowHeight / 2)
};

const pointLight = $('fePointLight');
const lightX = $('#anim_x');
const lightY = $('#anim_y');

// when the window is resized
$(window).on('resize', function() {

  // update lighting effect variables
  windowWidth = $(window).outerWidth();
  windowHeight = $(window).outerHeight();
  ratio = {
    x: lightDist / (windowWidth / 2),
    y: lightDist / (windowHeight / 2)
  };

});

// when the mouse is moved (desktop)
$(document).on('mousemove', function(e) {

  // get our current mouse position
  currentMousePos.x = e.pageX;
  currentMousePos.y = e.pageY;

  // if mouse is on right side of window
  if (currentMousePos.x > (windowWidth / 2)) {

    // calculate the positive light x position
    currentLightPos.x = ratio.x * (currentMousePos.x - (windowWidth / 2));

    // if mouse is on left side of window
  } else if (currentMousePos.x < (windowWidth / 2)) {

    // calculate the negative light x position
    currentLightPos.x = -lightDist + (ratio.x * currentMousePos.x);

  }

  // calculate the negative light y position
  // if mouse is on bottom side of window
  if (currentMousePos.y > (windowHeight / 2)) {

    // calculate the positive light y position
    currentLightPos.y = ratio.y * (currentMousePos.y - (windowHeight / 2));

    // if mouse is on top side of window
  } else if (currentMousePos.y < (windowHeight / 2)) {

    // calculate the negative light y position
    currentLightPos.y = -lightDist + (ratio.y * currentMousePos.y);

  }
  
  // console.log(currentLightPos.x, currentLightPos.y);

  // update shine filter fePointLight attributes
  lightX.attr({
    from: pointLight.prop('x').animVal,
    to: currentLightPos.x
  });
  lightX[0].beginElement();
  lightY.attr({
    from: pointLight.prop('y').animVal,
    to: currentLightPos.y
  });
  lightY[0].beginElement();

});
.circle {
  width: 100px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
<svg class="circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="50" filter="url(#shine)" />
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
    <filter id="shine" filterUnits="objectBoundingBox" x="-10%" y="-10%" width="150%" height="150%">
        <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur" />
        <feSpecularLighting in="blur" surfaceScale="15" specularConstant="2.5" specularExponent="200" result="specOut" lighting-color="white">
          <fePointLight x="-10000" y="-10000" z="8000">
            <animate id="anim_x" attributeName="x"
                     begin="indefinite" dur="0.5s" restart="always"
                     from="-10000" to="-10000" fill="freeze" />
            <animate id="anim_y" attributeName="y"
                     begin="indefinite" dur="0.5s" restart="always"
                     from="-10000" to="-10000" fill="freeze" />
          </fePointLight>
        </feSpecularLighting>
        <feComposite in="specOut" in2="SourceAlpha" operator="in" result="specOut2" />
        <feComposite in="SourceGraphic" in2="specOut2" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" />
    </filter>
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
2
ccprog 27 jul. 2020 a las 20:14