共计 5936 个字符,预计需要花费 15 分钟才能阅读完成。
编写一个互动(并且超级令人满意)的光标:7 个简单的步骤 + 2KB 的代码
近期我制作了这个光标动画,人们似乎很喜欢它 :)
这是一个很好看的作品,但同时也非常简单,只需 2KB 的 JS 代码。
而且,这种方法非常通用,可以作为其他美丽作品的模板使用。因此,它值得有一个逐步指南!
步骤 #1:设置
我们正在
canvas {
position: fixed;
top: 0;
left: 0;
}
setupCanvas();
window.addEventListener("resize", setupCanvas);
function setupCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
当然,我们需要跟踪光标位置。
const pointer = {
x: .5 * window.innerWidth,
y: .5 * window.innerHeight,
}
window.addEventListener("click", e => {updateMousePosition(e.clientX, e.clientY);
});
window.addEventListener("mousemove", e => {updateMousePosition(e.clientX, e.clientY);
});
window.addEventListener("touchmove", e => {updateMousePosition(e.targetTouches[0].clientX, e.targetTouches[0].clientY);
});
function updateMousePosition(eX, eY) {
pointer.x = eX;
pointer.y = eY;
}
步骤 #2:动画循环
要看到最简单的鼠标跟随动画,我们只需要使用该方法循环重画画布window.requestAnimationFrame(),并在每一步绘制以指针坐标为中心的圆。
const p = {x: 0, y: 0}; // coordinate to draw
update(0);
function update(t) {ctx.clearRect(0, 0, canvas.width, canvas.height);
// copy cursor position
p.x = poiner.x;
p.y = poiner.y;
// draw a dot
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();
window.requestAnimationFrame(update);
}
通过上面的代码,我们有一个跟随鼠标的黑色圆圈。
步骤 #3:添加延迟
现在,圆圈会尽可能快地跟随光标。让我们添加一个延迟,以便点以某种弹性方式赶上目标位置。
const params = {
// ...
spring: .4
};
// p.x = poiner.x;
// p.y = poiner.y;
p.x += (pointer.x - p.x) * params.spring;
p.y += (pointer.y - p.y) * params.spring;
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();
spring 参数用于确定点追上光标位置的速度。像 0.1 这样的小值会使其跟随非常慢,而 spring = 1 意味着没有延迟。
步骤 #3:创建鼠标轨迹
让我们创建一个点数据的轨迹数组,每个点都保存我们用来计算延迟的 x / y 坐标和 dx/ 增量。dy
const params = {
// ...
pointsNumber: 30
};
// const p = {x: 0, y: 0};
const trail = new Array(params.pointsNumber);
for (let i = 0; i 我们现在绘制整个轨迹,而不是单个点,其中每个点都试图赶上前一个点。第一个点追上了光标坐标 (pointer),并且第一个点的延迟更长 - 只是因为它对我来说看起来更好:)
function update(t) {ctx.clearRect(0, 0, canvas.width, canvas.height);
trail.forEach((p, pIdx) => {const prev = pIdx === 0 ? pointer : trail[pIdx - 1];
const spring = pIdx === 0 ? .4 * params.spring : params.spring;
p.dx = (prev.x - p.x) * spring;
p.dy = (prev.y - p.y) * spring;
p.x += p.dx;
p.y += p.dy;
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();});
window.requestAnimationFrame(update);
}
步骤 #4:将点转向线
绘制折线而不是点很容易。
trail.forEach((p, pIdx) =/> {const prev = pIdx === 0 ? pointer : trail[pIdx - 1];
p.dx = (prev.x - p.x) * params.spring;
p.dy = (prev.y - p.y) * params.spring;
p.x += p.dx;
p.y += p.dy;
// ctx.beginPath();
// ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
// ctx.fill();
if (pIdx === 0) {
// start the line on the first point
ctx.beginPath();
ctx.moveTo(p.x, p.y);
} else {
// continue with new line segment to the following one
ctx.lineTo(p.x, p.y);
}
});
// draw the thing
ctx.stroke();
步骤 #5:累积速度
使光标动画看起来非常漂亮的是累积增量。让我们不仅使用 dx/dy 来表示到邻居位置的距离,而且还累加这个距离。
为了防止增量值变得超级大超级快,我们还在每个步骤上将 dx/dy 与新参数相乘。friction
const params = {
// ...
friction: .5
};
...
// ...
// p.dx = (prev.x - p.x) * spring;
// p.dy = (prev.y - p.y) * spring;
p.dx += (prev.x - p.x) * spring;
p.dy += (prev.y - p.y) * spring;
p.dx *= params.friction;
p.dy *= params.friction;
// as before
p.x += p.dx;
p.y += p.dy;
// ...
步骤 #6:平滑线条
动议完成!让我们让笔画看起来更好,并将每条线段替换为贝塞尔曲线。
trail.forEach((p, pIdx) =/> {
// calc p.x and p.y
if (pIdx === 0) {ctx.beginPath();
ctx.moveTo(p.x, p.y);
// } else {// ctx.lineTo(p.x, p.y);
}
});
for (let i = 1; i 光滑的!
步骤#7:调整线宽
对于此演示,最后一步是将默认值 lineWidth1px 替换为每个段变得更小的动态值。
const params = {baseWidth: .9,};
...
for (let i = 1; i 文章来源:https://www.toymoban.com/diary/js/395.html
全源码示例
HTML
CSS
body, html {
padding: 0;
margin: 0;
overscroll-behavior: none;
}
/* for tutorial link only */
.links {
position: fixed;
bottom: 10px;
right: 10px;
font-size: 18px;
font-family: sans-serif;
background-color: white;
padding: 10px;
}
a {
text-decoration: none;
color: black;
margin-left: 1em;
}
a:hover {text-decoration: underline;}
a img.icon {
display: inline-block;
height: 1em;
margin: 0 0 -0.1em 0.3em;
}
JS
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext('2d');
// for intro motion
let mouseMoved = false;
const pointer = {
x: .5 * window.innerWidth,
y: .5 * window.innerHeight,
}
const params = {
pointsNumber: 40,
widthFactor: .3,
mouseThreshold: .6,
spring: .4,
friction: .5
};
const trail = new Array(params.pointsNumber);
for (let i = 0; i {updateMousePosition(e.pageX, e.pageY);
});
window.addEventListener("mousemove", e => {
mouseMoved = true;
updateMousePosition(e.pageX, e.pageY);
});
window.addEventListener("touchmove", e => {
mouseMoved = true;
updateMousePosition(e.targetTouches[0].pageX, e.targetTouches[0].pageY);
});
function updateMousePosition(eX, eY) {
pointer.x = eX;
pointer.y = eY;
}
setupCanvas();
update(0);
window.addEventListener("resize", setupCanvas);
function update(t) {
// for intro motion
if (!mouseMoved) {pointer.x = (.5 + .3 * Math.cos(.002 * t) * (Math.sin(.005 * t))) * window.innerWidth;
pointer.y = (.5 + .2 * (Math.cos(.005 * t)) + .1 * Math.cos(.01 * t)) * window.innerHeight;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
trail.forEach((p, pIdx) => {const prev = pIdx === 0 ? pointer : trail[pIdx - 1];
const spring = pIdx === 0 ? .4 * params.spring : params.spring;
p.dx += (prev.x - p.x) * spring;
p.dy += (prev.y - p.y) * spring;
p.dx *= params.friction;
p.dy *= params.friction;
p.x += p.dx;
p.y += p.dy;
});
ctx.beginPath();
ctx.moveTo(trail[0].x, trail[0].y);
for (let i = 1; i
文章来源地址 https://www.toymoban.com/diary/js/395.html
到此这篇关于使用 Javascript 编写一个鼠标交互式跟随特效和光标交互效果的文章就介绍到这了, 更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持 TOY 模板网!