Box-shadow responds to mouse position

Viewed 1241

I'm trying to create an animation in a webpage where an element's box-shadow responds to the mouse's position, i.e. (red X = mouse):

enter image description here

enter image description here

enter image description here

I already found a function that tracks the mouse movement, but I don't know how to apply this to the object. This is my code:

$(document).ready(function() {
  function shadowAnimation() {
    var objectToAnimate = $("#shadow-test");
    document.onmousemove = handleMouseMove;

    function handleMouseMove(event) {
      var eventDoc, doc, body;

      event = event || window.event;

      if (event.pageX == null && event.clientX != null) {
        eventDoc = (event.target && event.target.ownerDocument) || document;
        doc = eventDoc.documentElement;
        body = eventDoc.body;

        event.pageX = event.clientX +
          (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
          (doc && doc.clientLeft || body && body.clientLeft || 0);
        event.pageY = event.clientY +
          (doc && doc.scrollTop || body && body.scrollTop || 0) -
          (doc && doc.clientTop || body && body.clientTop || 0);
      }

      console.log(event.pageX + " " + event.pageY);

    }
  }
});
#shadow-test {
  box-shadow: -10px -10px red;
  border: 1px solid white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p id="shadow-test">This is a shadow test</p>

3 Answers

Your logic to capture the mouse position is much more complicated than it needs to be. You only need to track pageX and pageY of the mousemove event.

To move the shadow you simply need to work out the distance from the mouse to the mid-point of each dimension of the target element. Then you can apply that distance, limited by the available height of the element, to the size of the box-shadow, something like this:

jQuery($ => {
  let $shadow = $('#shadow-test');
  let shadowMax = $shadow.height();
  let shadowMin = shadowMax * -1;
  let shadowMidPoints = [
    $shadow.offset().left + $shadow.width() / 2,
    $shadow.offset().top + $shadow.height() / 2
  ];

  $(document).on('mousemove', e => {
    let shadowX = Math.min(shadowMax, Math.max(shadowMin, shadowMidPoints[0] - e.pageX));
    let shadowY = Math.min(shadowMax, Math.max(shadowMin, shadowMidPoints[1] - e.pageY));
    $shadow.css('box-shadow', `${shadowX}px ${shadowY}px red`);
  });
});
body {
  height: 2000px;
}

#shadow-test {
  box-shadow: -10px -10px red;
  border: 1px solid white;
  margin: 100px;
  background-color: #CCC;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<p id="shadow-test">This is a shadow test</p>

Please take a look at the following examples(sans jquery).

const p = document.querySelector('#shadow-test');

const clamp = (a, m, n) => {
  const max = Math.max(m, n);
  const min = Math.min(m, n);

  return Math.max(min, Math.min(max, a));
};

const MAX_SHADOW_OFFSET = 30;

const paint = (x, y) => {
  const r = p.getBoundingClientRect();
  const o = Math.min(r.width, r.height, MAX_SHADOW_OFFSET); // compute max shadow offset
  
  const mx = clamp(x, r.left - o, r.right + o); // clamp mouse coordinates within the shadow projection bounding box.
  const my = clamp(y, r.top - o, r.bottom + o);
  const px = r.right - r.width / 2; // compute element bb midpoints.
  const py = r.bottom - r.height / 2;
  const nx = (mx - px) / (r.right - r.left + 2 * o); // project mouse position relative to the bounding box to [-.5, .5];
  const ny = (my - py) / (r.bottom - r.top + 2 * o); 
  
  requestAnimationFrame(() => {
    p.style.boxShadow = `${-1 * nx * o}px ${-1 * ny * o}px var(--shadow-color)`;
  });
};

document.addEventListener('mousemove', (e) => paint(e.clientX, e.clientY), {
  passive: true
});
:root {
  --primary-color: black;
  --secondary-color: white;
  --shadow-color: red;
  --button-color: blue;
  --color: white;
}

body {
  background-color: var(--primary-color);
  padding: 1rem;
}

.center {
  text-align: center;
}

button {
 background-color: var(--button-color);
 color: var(--color);
 border: 0;
 border-radius: .25rem;
 padding: .375rem 1rem;
}

#shadow-test {
  border: 1px solid var(--secondary-color);
  color: var(--secondary-color);
}
<div class="center">
  <button>
    More
  </button>
</div>

<p id="shadow-test">This is a shadow test</p>

Its definitely a mouthful compared to the other solutions but its not all that complex. The algorithm can be broken down into a couple of steps.

  1. We figure out the bounding box of the element. We need this to figure out the max offset for our box shadow as well as projections to our normalize coordinate space later on.

  2. We clamp the mouse coordinates to our new bounding box. This is used so that if you move your mouse far away from the element it doesn't continue moving. We also will need the mouse coordinates to project our to our normalized coordinate space.

  3. We find the midpoint of our element. Anything left of the midpoint will be negative, anything right will be positive and so on. We can use the sign values to figure what side the box shadow should be. A mouse on the left side of the midpoint will give use a negative value, on the right side it will be positive.

  4. Finally we project our mouse coordinates to our normalized coordinate space of [-0.5, .5]. The normalized coordinates makes it super easy to compute offsets by just simple multiplication. Think of (nx,ny) as just scale values. Given a max shadow offset value, how much should we apply to the style? and what direction?

Other notes

Im updating the style in an request animation frame. This is typically better for performance. You should also note the css variables. This is done so i don't have to hard code the color value of the box-shadow in code. The theme lives completely in css.

As I understand you implemented tracking of mouse. In your screenshot and description its not clear when box-shadow property changes according to mouse position. Once you catch mouse position and can calculate position of where mouse pointer is then just use this js code to update box-shadow property of your text input. three case

$("#shadow-test").css('box-shadow', '-10px -10px red');
$("#shadow-test").css('box-shadow', '10px 10px red');
$("#shadow-test").css('box-shadow', '0px 10px red');
Related