Use absolute coordinates with transform translate

Viewed 367

I can make a box that follows the mouse with the following.

document.addEventListener('mousemove', (e) => {
    document.documentElement.style.setProperty('--mouse-x', e.clientX + 'px');
    document.documentElement.style.setProperty('--mouse-y', e.clientY + 'px');
});
.box {
  width: 50px;
  height: 50px;
  
  background-color: blue;
  
  transform: translate(calc(var(--mouse-x) - 50%), calc(var(--mouse-y) - 50%));
}
<div class="box"></div>

but as soon as the element is not positioned at the top left, it breaks.

document.addEventListener('mousemove', (e) => {
    document.documentElement.style.setProperty('--mouse-x', e.clientX + 'px');
    document.documentElement.style.setProperty('--mouse-y', e.clientY + 'px');
});
.box {
  width: 50px;
  height: 50px;
  
  margin-left: 150px;
  
  background-color: blue;
  
  transform: translate(calc(var(--mouse-x) - 50%), calc(var(--mouse-y) - 50%));
}
<div class="box"></div>

How can I use absolute coordinates with a transform? I don't want to use left/top/position: fixed/absolute because I need to preserve the position of the element in the flow.

I could use JavaScript to grab the central position and then use that infomation to get the correct center.

document.addEventListener('mousemove', (e) => {
    document.documentElement.style.setProperty('--mouse-x', e.clientX + 'px');
    document.documentElement.style.setProperty('--mouse-y', e.clientY + 'px');
});

window.addEventListener('load', (e) => {
    const box = document.querySelector('.box');
    const rect = box.getBoundingClientRect();
    box.setAttribute('style', `
      --center-x: ${rect.left + (rect.width / 2)}px;
      --center-y: ${rect.top + (rect.height / 2)}px;
    `);
});
.box {
  width: 50px;
  height: 50px;
  
  margin-left: 150px;
  
  background-color: blue;
  
  transform: translate(calc(var(--mouse-x) - var(--center-x)), calc(var(--mouse-y) - var(--center-y)));
}
<div class="box"></div>

This works, but it's not ideal and easily broken if anything else in the page changes. It also slows down with more elements, and I'd like it to be as fast as possible. It there a better way to do this? I'm fine using CSS/Vanilla JS.

2 Answers

You don't have to use translate() please I would recommend you to use the left and top properties in CSS. They can help you position an element based on coordinates.

window.addEventListener('load', (e) => {
    const box = document.querySelector('.box');
    const rect = box.getBoundingClientRect();
    document.addEventListener('mousemove', (e) => {
    box.style.left = e.pageX + 'px';
    box.style.top = e.pageY + 'px';
});
});
.box {
  width: 50px;
  height: 50px;
  position:absolute;
  transform:translate(-50%,-50%);
  background-color: blue;
}
<div class="box"></div>

The transform: translate() property works relative to the size of the box but the left and top properties don't. It can also be much faster in some cases because in your code there was a lot of calculation going on. Whereas, this is straightforward.

Here is a solution using the requestAnimationFrame() function. The requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint. For higher refresh rate monitors, this function will be run more the number of times to match the refresh rate.

More information here

Here is the working solution:

const box = document.querySelector('.box');
const rect = box.getBoundingClientRect();
let mouseX = 0;
let mouseY = 0
document.addEventListener('mousemove', (e) => {
  mouseX = e.pageX + 'px';
  mouseY = e.pageY + 'px';
})

function mouseMove() {
  box.style.left = mouseX;
  box.style.top = mouseY;
  requestAnimationFrame(mouseMove)
};
mouseMove()
.box {
  width: 50px;
  height: 50px;
  position: absolute;
  transform: translate(-50%, -50%);
  background-color: blue;
}
<div class="box"></div>

Related