jQuery: 滚动时鼠标跟随元素不会粘在光标上

jQuery: mouse following element wont stick with cursor when scrolling

我有一个小问题,无法解决。

我开发了一个跟随光标的元素,它需要运行的方式是光标周围的边框需要与光标的位置保持一致。但是我现在遇到的问题是向下滚动时它不会卡住。

你可以在下面的演示中查看我的意思。

问题似乎是没有正确检查页面的高度,这就是为什么它没有正确定位的原因。我说得对吗?

const windowW = window.innerWidth;
const windowH = window.innerHeight;
const maxLength = Math.max(windowW, windowH);

const cursorWidth = 100;
const cursorR = cursorWidth >> 1;
const cursorDelay = 10;

const buttons = Array.from(document.querySelectorAll('.border-button'));

const cursor = {
    el: document.querySelector('.border-cursor'),
    x: windowW >> 1,
    y: windowH >> 1,
    scaleX: 1,
    scaleY: 1,
};

const target = {
    x: windowW >> 1,
    y: windowH >> 1,
    width: cursorWidth,
    followMouse: true,
};

const norm = (val, max, min) => (val - min) / (max - min);
const toDegrees = r => r * (180 / Math.PI);
const distanceBetween = (v1, v2) => Math.sqrt((v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y +- v2.y));

const loop = () => {
    const destX = target.x - cursorR;
    const destY = target.y - cursorR;

    const newX = cursor.x + ((destX - cursor.x) / cursorDelay);
    const newY = cursor.y + ((destY - cursor.y) / cursorDelay);
    const angle = angleBetween(cursor.x, cursor.y, newX, newY);

    if (target.followMouse) {
        const distance = Math.abs(distanceBetween(target, cursor));
        const scale = norm(distance, maxLength, cursorR);
        cursor.scaleX = 1 + scale;
        cursor.scaleY = 1 - scale;
    } else {
        const targetScale = target.width / cursorWidth;

        cursor.scaleX += (targetScale - cursor.scaleX) / (cursorDelay / 2);
        cursor.scaleY = cursor.scaleX;
    }

    cursor.x = newX;
    cursor.y = newY;

    cursor.el.style.transform = `translate(${cursor.x}px, ${cursor.y}px) rotate(${toDegrees(angle)}deg) scale(${cursor.scaleX}, ${cursor.scaleY})`;

    requestAnimationFrame(loop);
};

const angleBetween = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1);

const onPointerMove = (e) => {
    if (!target.followMouse) {
        return;
    }

    const pointer = (e.touches && e.touches.length) ? e.touches[0] : e;
    const { clientX: x, clientY: y } = pointer;

    target.x = x;
    target.y = y;
};

const onPointerOver = (e) => {
    const btn = e.target;
    const rect = btn.getBoundingClientRect();

    target.followMouse = false;
    target.x = rect.left + (rect.width >> 1);
    target.y = rect.top + (rect.height >> 1);

    target.width = Math.max(rect.width, rect.height) + 50;
};

const onPointerOut = () => {
    target.followMouse = true;
    target.width = cursorWidth;
};

document.body.addEventListener('mousemove', onPointerMove);
document.body.addEventListener('touchmove', onPointerMove);

buttons.forEach((btn) => {
    btn.addEventListener('touchstart', onPointerOver);
    btn.addEventListener('mouseover', onPointerOver);

    btn.addEventListener('touchend', onPointerOut);
    btn.addEventListener('mouseout', onPointerOut);
});

loop();

const windowW = window.innerWidth;
const windowH = window.innerHeight;
const maxLength = Math.max(windowW, windowH);

const cursorWidth = 100;
const cursorR = cursorWidth >> 1;
const cursorDelay = 10;

const buttons = Array.from(document.querySelectorAll('.border-button'));

const cursor = {
 el: document.querySelector('.border-cursor'),
 x: windowW >> 1,
 y: windowH >> 1,
 scaleX: 1,
 scaleY: 1,
};

const target = {
 x: windowW >> 1,
 y: windowH >> 1,
 width: cursorWidth,
 followMouse: true,
};

const norm = (val, max, min) => (val - min) / (max - min);
const toDegrees = r => r * (180 / Math.PI);
const distanceBetween = (v1, v2) => Math.sqrt((v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y +- v2.y));

const loop = () => {
 const destX = target.x - cursorR;
 const destY = target.y - cursorR;

 const newX = cursor.x + ((destX - cursor.x) / cursorDelay);
 const newY = cursor.y + ((destY - cursor.y) / cursorDelay);
 const angle = angleBetween(cursor.x, cursor.y, newX, newY);

 if (target.followMouse) {
  const distance = Math.abs(distanceBetween(target, cursor));
  const scale = norm(distance, maxLength, cursorR);
  cursor.scaleX = 1 + scale;
  cursor.scaleY = 1 - scale;
 } else {
  const targetScale = target.width / cursorWidth;

  cursor.scaleX += (targetScale - cursor.scaleX) / (cursorDelay / 2);
  cursor.scaleY = cursor.scaleX;
 }

 cursor.x = newX;
 cursor.y = newY;

 cursor.el.style.transform = `translate(${cursor.x}px, ${cursor.y}px) rotate(${toDegrees(angle)}deg) scale(${cursor.scaleX}, ${cursor.scaleY})`;

 requestAnimationFrame(loop);
};

const angleBetween = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1);

const onPointerMove = (e) => {
 if (!target.followMouse) {
  return;
 }

 const pointer = (e.touches && e.touches.length) ? e.touches[0] : e;
 const { clientX: x, clientY: y } = pointer;

 target.x = x;
 target.y = y;
};

const onPointerOver = (e) => {
 const btn = e.target;
 const rect = btn.getBoundingClientRect();

 target.followMouse = false;
 target.x = rect.left + (rect.width >> 1);
 target.y = rect.top + (rect.height >> 1);

 target.width = Math.max(rect.width, rect.height) + 50;
};

const onPointerOut = () => {
 target.followMouse = true;
 target.width = cursorWidth;
};

document.body.addEventListener('mousemove', onPointerMove);
document.body.addEventListener('touchmove', onPointerMove);

buttons.forEach((btn) => {
 btn.addEventListener('touchstart', onPointerOver);
 btn.addEventListener('mouseover', onPointerOver);

 btn.addEventListener('touchend', onPointerOut);
 btn.addEventListener('mouseout', onPointerOut);
});

loop();
html,
body {
  margin: 0;
  padding: 0;
}

.wrapper {
  width: 100vw;
  min-height: 1500px;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.container {
  width: 100%;
  display: flex;
  padding: 0 1rem;
}

.cursor {
  position: absolute;
  z-index: 10;
  width: 100px;
  height: 100px;
  border: 2px solid #23bfa0;
  border-radius: 50%;

  pointer-events: none;
}

.button {
  padding: 1rem;

  background-color: #23bfa0;
  border: none;
  box-shadow: 0 0 7px 0px rgba(0, 0, 0, 0.2);

  color: white;
  font-size: 1.2rem;

  cursor: pointer;

  transition: box-shadow 0.1s ease-in, transform 0.1s ease-in;
  
  &--small {
    padding: 0.75rem;
    font-size: 0.75rem;
  }
  
  &:hover {
    transform: translate(0%, -2px);
    box-shadow: 0px 4px 9px 2px rgba(0, 0, 0, 0.2)
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="cursor border-cursor"></div>

<div class="wrapper">
  <div class="container">
    <button class="button button--small border-button">small</button>
    <button class="button border-button">hover me</button>
    <button class="button border-button">hover me more</button>
  </div>
</div>
</body>

onPointerMove 中,尝试替换:

const { clientX: x, clientY: y } = pointer;

与:

const { pageX: x, pageY: y } = pointer;

下面post 很好地解释了这些值之间的差异:

此外,更改:

target.x = rect.left + (rect.width >> 1);
target.y = rect.top + (rect.height >> 1);

进入:

target.x = window.scrollX + rect.left + (rect.width >> 1);
target.y = window.scrollY + rect.top + (rect.height >> 1);

这会在计算按钮位置时考虑滚动。

演示:

const windowW = window.innerWidth;
const windowH = window.innerHeight;
const maxLength = Math.max(windowW, windowH);

const cursorWidth = 100;
const cursorR = cursorWidth >> 1;
const cursorDelay = 10;

const buttons = Array.from(document.querySelectorAll('.border-button'));

const cursor = {
 el: document.querySelector('.border-cursor'),
 x: windowW >> 1,
 y: windowH >> 1,
 scaleX: 1,
 scaleY: 1,
};

const target = {
 x: windowW >> 1,
 y: windowH >> 1,
 width: cursorWidth,
 followMouse: true,
};

const norm = (val, max, min) => (val - min) / (max - min);
const toDegrees = r => r * (180 / Math.PI);
const distanceBetween = (v1, v2) => Math.sqrt((v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y +- v2.y));

const loop = () => {
 const destX = target.x - cursorR;
 const destY = target.y - cursorR;

 const newX = cursor.x + ((destX - cursor.x) / cursorDelay);
 const newY = cursor.y + ((destY - cursor.y) / cursorDelay);
 const angle = angleBetween(cursor.x, cursor.y, newX, newY);

 if (target.followMouse) {
  const distance = Math.abs(distanceBetween(target, cursor));
  const scale = norm(distance, maxLength, cursorR);
  cursor.scaleX = 1 + scale;
  cursor.scaleY = 1 - scale;
 } else {
  const targetScale = target.width / cursorWidth;

  cursor.scaleX += (targetScale - cursor.scaleX) / (cursorDelay / 2);
  cursor.scaleY = cursor.scaleX;
 }

 cursor.x = newX;
 cursor.y = newY;

 cursor.el.style.transform = `translate(${cursor.x}px, ${cursor.y}px) rotate(${toDegrees(angle)}deg) scale(${cursor.scaleX}, ${cursor.scaleY})`;

 requestAnimationFrame(loop);
};

const angleBetween = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1);

const onPointerMove = (e) => {
 if (!target.followMouse) {
  return;
 }

 const pointer = (e.touches && e.touches.length) ? e.touches[0] : e;
 const { pageX: x, pageY: y } = pointer;

 target.x = x;
 target.y = y;
};

const onPointerOver = (e) => {
 const btn = e.target;
 const rect = btn.getBoundingClientRect();

 target.followMouse = false;
 target.x = window.scrollX + rect.left + (rect.width >> 1);
 target.y = window.scrollY + rect.top + (rect.height >> 1);

 target.width = Math.max(rect.width, rect.height) + 50;
};

const onPointerOut = () => {
 target.followMouse = true;
 target.width = cursorWidth;
};

document.body.addEventListener('mousemove', onPointerMove);
document.body.addEventListener('touchmove', onPointerMove);

buttons.forEach((btn) => {
 btn.addEventListener('touchstart', onPointerOver);
 btn.addEventListener('mouseover', onPointerOver);

 btn.addEventListener('touchend', onPointerOut);
 btn.addEventListener('mouseout', onPointerOut);
});

loop();
html,
body {
  margin: 0;
  padding: 0;
}

.wrapper {
  width: 100vw;
  min-height: 1500px;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.container {
  width: 100%;
  display: flex;
  padding: 0 1rem;
}

.cursor {
  position: absolute;
  z-index: 10;
  width: 100px;
  height: 100px;
  border: 2px solid #23bfa0;
  border-radius: 50%;

  pointer-events: none;
}

.button {
  padding: 1rem;

  background-color: #23bfa0;
  border: none;
  box-shadow: 0 0 7px 0px rgba(0, 0, 0, 0.2);

  color: white;
  font-size: 1.2rem;

  cursor: pointer;

  transition: box-shadow 0.1s ease-in, transform 0.1s ease-in;
  
  &--small {
    padding: 0.75rem;
    font-size: 0.75rem;
  }
  
  &:hover {
    transform: translate(0%, -2px);
    box-shadow: 0px 4px 9px 2px rgba(0, 0, 0, 0.2)
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="cursor border-cursor"></div>

<div class="wrapper">
  <div class="container">
    <button class="button button--small border-button">small</button>
    <button class="button border-button">hover me</button>
    <button class="button border-button">hover me more</button>
  </div>
</div>
</body>