gpt4 book ai didi

javascript - 如何通过鼠标移动 Angular 旋转正方形跟随鼠标?

转载 作者:行者123 更新时间:2023-12-04 17:11:09 26 4
gpt4 key购买 nike

我的光标后面有一个正方形。它的边框顶部是红色的,以查看旋转是否正确。我正在尝试根据鼠标移动 Angular 旋转它。就像如果鼠标向右上方移动 45 度,则正方形必须旋转 45 度。问题是,当我慢慢移动鼠标时,正方形开始疯狂旋转。但是,如果我将鼠标移动得足够快,方 block 就会非常平滑地旋转。

实际上,这只是我要完成的任务的一部分。我的全部任务是制作在鼠标移动时拉伸(stretch)的自定义圆形光标。我试图实现的想法:通过鼠标移动 Angular 旋转圆圈,然后对其进行缩放以产生拉伸(stretch)效果。但由于我上面描述的问题,我不能这样做。当鼠标速度较慢时,我需要我的追随者平稳旋转。

class Cursor {
constructor() {
this.prevX = null;
this.prevY = null;
this.curX = null;
this.curY = null;
this.angle = null;

this.container = document.querySelector(".cursor");
this.follower = this.container.querySelector(".cursor-follower");

document.addEventListener("mousemove", (event) => {
this.curX = event.clientX;
this.curY = event.clientY;
});

this.position();
}


position(timestamp) {
this.follower.style.top = `${this.curY}px`;
this.follower.style.left = `${this.curX}px`;


this.angle = Math.atan2(this.curY - this.prevY, this.curX - this.prevX) * 180/Math.PI;
console.log(this.angle + 90);

this.follower.style.transform = `rotateZ(${this.angle + 90}deg)`;

this.prevX = this.curX;
this.prevY = this.curY;

requestAnimationFrame(this.position.bind(this));
}
}


const cursor = new Cursor();
.cursor-follower {
position: fixed;
top: 0;
left: 0;
z-index: 9999;

pointer-events: none;
user-select: none;

width: 76px;
height: 76px;
margin: -38px;
border: 1.5px solid #000;

border-top: 1.5px solid red;
}
<div class="cursor">
<div class="cursor-follower"></div>
</div>

最佳答案

平滑地跟随光标切线并不像第一感觉那么简单。在现代浏览器中,mousemove 事件以帧速率(通常为 60 FPS)在附近触发。当鼠标缓慢移动时,光标在事件之间仅移动一两个像素。计算 Angular 时,垂直+水平移动1px解析为45deg。然后还有一个问题,事件触发率不一致,在鼠标移动过程中,事件触发率可能会下降到 30 FPS 甚至 24 FPS,这实际上有助于获得更准确的 Angular ,但会导致比例计算严重不准确(你的真正任务似乎也需要规模计算)。

一种解决方案是使用 CSS Transitions使动画更流畅。但是,添加过渡会使 Angular 计算复杂得多,因为负 Angular 和正 Angular 之间的跳跃 Math.atan2交叉时返回 PI 将在使用转换时变得可见。

下面是一个示例代码,说明如何使用过渡使光标跟随器更平滑。

class Follower {
// Default options
threshold = 4;
smoothness = 10;
stretchRate = 100;
stretchMax = 100;
stretchSlow = 100;
baseAngle = Math.PI / 2;
// Class initialization
initialized = false;
// Listens mousemove event
static moveCursor (e) {
if (Follower.active) {
Follower.prototype.crsrMove.call(Follower.active, e);
}
}
static active = null;
// Adds/removes mousemove listener
static init () {
if (this.initialized) {
document.removeEventListener('mousemove', this.moveCursor);
if (this.active) {
this.active.cursor.classList.add('hidden');
}
} else {
document.addEventListener('mousemove', this.moveCursor);
}
this.initialized = !this.initialized;
}
// Base values of instances
x = -1000;
y = -1000;
angle = 0;
restoreTimer = -1;
stamp = 0;
speed = [0];
// Prototype properties
constructor (selector) {
this.cursor = document.querySelector(selector);
this.restore = this.restore.bind(this);
}
// Activates a new cursor
activate (options = {}) {
// Remove the old cursor
if (Follower.active) {
Follower.active.cursor.classList.add('hidden');
Follower.active.cursor.classList.remove('cursor', 'transitioned');
}
// Set the new cursor
Object.assign(this, options);
this.setCss = this.cursor.style.setProperty.bind(this.cursor.style);
this.cursor.classList.remove('hidden');
this.cHW = this.cursor.offsetWidth / 2;
this.cHH = this.cursor.offsetHeight / 2;
this.setCss('--smoothness', this.smoothness / 100 + 's');
this.cursor.classList.add('cursor');
setTimeout(() => this.cursor.classList.add('transitioned'), 0); // Snap to the current angle
this.crsrMove({
clientX: this.x,
clientY: this.y
});
Follower.active = this;
return this;
}
// Moves the cursor with effects
crsrMove (e) {
clearTimeout(this.restoreTimer); // Cancel reset timer
const PI = Math.PI,
pi = PI / 2,
x = e.clientX,
y = e.clientY,
dX = x - this.x,
dY = y - this.y,
dist = Math.hypot(dX, dY);
let rad = this.angle + this.baseAngle,
dTime = e.timeStamp - this.stamp,
len = this.speed.length,
sSum = this.speed.reduce((a, s) => a += s),
speed = dTime
? ((1000 / dTime) * dist + sSum) / len
: this.speed[len - 1], // Old speed when dTime = 0
scale = Math.min(
this.stretchMax / 100,
Math.max(speed / (500 - this.stretchRate || 1),
this.stretchSlow / 100
)
);
// Update base values and rotation angle
if (isNaN(dTime)) {
scale = this.scale;
} // Prevents a snap of a new cursor
if (len > 5) {
this.speed.length = 1;
}
// Update angle only when mouse has moved enough from the previous update
if (dist > this.threshold) {
let angle = Math.atan2(dY, dX),
dAngle = angle - this.angle,
adAngle = Math.abs(dAngle),
cw = 0;
// Smoothen small angles
if (adAngle < PI / 90) {
angle += dAngle * 0.5;
}
// Crossing ±PI angles
if (adAngle >= 3 * pi) {
cw = -Math.sign(dAngle) * Math.sign(dX); // Rotation direction: -1 = CW, 1 = CCW
angle += cw * 2 * PI - dAngle; // Restores the current position with negated angle
// Update transform matrix without transition & rendering
this.cursor.classList.remove('transitioned');
this.setCss('--angle', `${angle + this.baseAngle}rad`);
this.cursor.offsetWidth; // Matrix isn't updated without layout recalculation
this.cursor.classList.add('transitioned');
adAngle = 0; // The angle was handled, prevent further adjusts
}
// Orthogonal mouse turns
if (adAngle >= pi && adAngle < 3 * pi) {
this.cursor.classList.remove('transitioned');
setTimeout(() => this.cursor.classList.add('transitioned'), 0);
}
rad = angle + this.baseAngle;
this.x = x;
this.y = y;
this.angle = angle;
}
this.scale = scale;
this.stamp = e.timeStamp;
this.speed.push(speed);
// Transform the cursor
this.setCss('--angle', `${rad}rad`);
this.setCss('--scale', `${scale}`);
this.setCss('--tleft', `${x - this.cHW}px`);
this.setCss('--ttop', `${y - this.cHH}px`);
// Reset the cursor when mouse stops
this.restoreTimer = setTimeout(this.restore, this.smoothness + 100, x, y);
}
// Returns the position parameters of the cursor
position () {
const {x, y, angle, scale, speed} = this;
return {x, y, angle, scale, speed};
}
// Restores the cursor
restore (x, y) {
this.state = 0;
this.setCss('--scale', 1);
this.scale = 1;
this.speed = [0];
this.x = x;
this.y = y;
}
}
Follower.init();

const crsr = new Follower('.crsr').activate();
body {
margin: 0px;
}

.crsr {
width: 76px;
height: 76px;
border: 2px solid #000;
border-radius: 0%;
text-align: center;
font-size: 20px;
}

.cursor {
position: fixed;
cursor: default;
user-select: none;
left: var(--tleft);
top: var(--ttop);
transform: rotate(var(--angle)) scaleY(var(--scale));
}

.transitioned {
transition: transform var(--smoothness) linear;
}

.hidden {
display: none;
}
<div class="crsr hidden">A</div>

代码的基本思想是等到鼠标移动了足够多的像素(threshold)来计算 Angular 。 “疯狂圆圈”效应通过将 Angular 设置为相同位置来解决,但在穿过 PI 时处于取反的 Angular 。这种变化在渲染之间是无形的。

CSS 变量用于 transform 中的实际值,这允许同时更改转换函数的单个参数,而不必重写整个规则。 setCss 方法只是语法糖,它使代码更短一些。

当前参数显示了一个矩形跟随器,就像您的问题一样。设置前。 stretchMax = 300stretchSlow = 125 并向 CSS 添加 50% 的边框半径可能接近您最终需要的。 stretchRate 定义与鼠标速度相关的拉伸(stretch)。如果慢动作对于您的目的仍然不够平滑,您可以创建一个更好的算法来 //Smoothen small angles 部分(在 crsrMove 方法中)。您可以在 jsFiddle 处使用参数.

关于javascript - 如何通过鼠标移动 Angular 旋转正方形跟随鼠标?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69422887/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com