使用Javascript编写一个鼠标交互式跟随特效和光标交互效果

编写一个互动(并且超级令人满意)的光标:7个简单的步骤 + 2KB的代码

近期我制作了这个光标动画,人们似乎很喜欢它 :)

这是一个很好看的作品,但同时也非常简单,只需2KB的JS代码。

而且,这种方法非常通用,可以作为其他美丽作品的模板使用。因此,它值得有一个逐步指南!

鼠标光标特效结果

步骤#1:设置

我们正在<canvas>元素上绘图,并且需要<canvas>全屏显示。

canvas {
    position: fixed;
    top: 0;
    left: 0;
}
<canvas></canvas>
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 < params.pointsNumber; i++) {
    trail[i] = {
        x: poiner.x,
        y: poiner.y,
        dx: 0,
        dy: 0,
    }
}

我们现在绘制整个轨迹,而不是单个点,其中每个点都试图赶上前一个点。第一个点追上了光标坐标 ( 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 < trail.length - 1; i++) {
    const xc = .5 * (trail[i].x + trail[i + 1].x);
    const yc = .5 * (trail[i].y + trail[i + 1].y);
    ctx.quadraticCurveTo(trail[i].x, trail[i].y, xc, yc);
}

ctx.stroke();

光滑的!

线条流畅效果

步骤#7:调整线宽

对于此演示,最后一步是将默认值lineWidth1px 替换为每个段变得更小的动态值。

const params = {
    baseWidth: .9,
};
...

for (let i = 1; i < trail.length - 1; i++) {
    // ...
    ctx.quadraticCurveTo(trail[i].x, trail[i].y, xc, yc);
    ctx.lineWidth = params.baseWidth * (params.pointsNumber - i);
}

最终曲线效果

全源码示例

HTML

<canvas></canvas>


<div class="links">
    <a href="https://www.toymoban.com" target="_blank">tutorial<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>    </a>
</div>

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 < params.pointsNumber; i++) {
    trail[i] = {
        x: pointer.x,
        y: pointer.y,
        dx: 0,
        dy: 0,
    }
}

window.addEventListener("click", e => {
    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 < trail.length - 1; i++) {
        const xc = .5 * (trail[i].x + trail[i + 1].x);
        const yc = .5 * (trail[i].y + trail[i + 1].y);
        ctx.quadraticCurveTo(trail[i].x, trail[i].y, xc, yc);
        ctx.lineWidth = params.widthFactor * (params.pointsNumber - i);
        ctx.stroke();
    }
    ctx.lineTo(trail[trail.length - 1].x, trail[trail.length - 1].y);
    ctx.stroke();
   
    window.requestAnimationFrame(update);
}

function setupCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;


文章来源地址https://www.toymoban.com/diary/js/395.html

到此这篇关于使用Javascript编写一个鼠标交互式跟随特效和光标交互效果的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

原文地址:https://www.toymoban.com/diary/js/395.html

如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用
Laravel 的高效 API 交互和使用:初学者指南
上一篇 2023年10月14日 00:55
下一篇 2023年10月14日 02:11

相关文章

  • Python+turtle交互式绘图:可以用鼠标拖动的小海龟

    功能描述:代码运行后,在窗口上显示3个小海龟,使用鼠标拖动小海龟时可以动态改变窗口颜色,如下图所示。 说明:本例代码主体部分来自turtle Demo,我稍微修改了一下,重点增加了注释,方便阅读和理解。 参考代码: ----------相关阅读---------- 教学课件 1900页Python系列P

    2023年04月08日
    浏览(52)
  • 创建交互式用户体验:探索JavaScript中的Prompt功能

    在前端开发中,JavaScript的 prompt() 函数是一个强大而有用的工具,它可以创建交互式的用户体验。无论是接收用户输入、进行简单的验证还是实现高级的交互功能, prompt() 函数都能胜任。本篇博客将深入探讨 prompt() 函数的用法、最佳实践和一些示例代码,为您展示如何利用它

    2024年02月15日
    浏览(44)
  • 19个Web前端交互式3D JavaScript框架和库

    JavaScript (JS) 是一种轻量级的解释(或即时编译)编程语言,是世界上最流行的编程语言。JavaScript 是一种基于原型的多范式、单线程的动态语言,支持面向对象、命令式和声明式(例如函数式编程)风格。JavaScript 几乎可以做任何事情,更可以在包括物联网在内的多个平台

    2024年02月22日
    浏览(45)
  • 构建一个动态交互式图表

    在Web开发中,JavaScript不仅是实现交互效果的关键,还可以用于构建复杂的可视化组件,如动态交互式图表。在本篇博客中,我将演示如何使用JavaScript和HTML5的Canvas元素来创建一个简单的动态条形图。 HTML结构  首先,我们需要一个HTML结构来容纳我们的图表。 JavaScript实现 接下

    2024年02月20日
    浏览(51)
  • 用 ChatGPT 尝试 JavaScript 交互式学习体验,有用但不完美

    很好,但还不能取代专家导师,有时还会犯错! ChatGPT 教小狗编程( Midjourney 创作) GPT-4刚刚发布,相较于GPT-3.5,它有显著的增强功能。其中之一是它在更长时间的交互和更大的提示下,能够更好地保持连贯性。 多年来,我一直致力于建立前端教学网站,为JavaScript开发人员

    2024年02月02日
    浏览(47)
  • Linux【脚本 05】交互式shell脚本编写及问题处理([: ==: unary operator expected)[: ==: 期待一元表达式

    之前写了Windows的cmd脚本用来保存报告文件: 但是有时候服务仅在Linux环境上进行部署,所以要写一个shell脚本进行报告的保存。 2.1 初始版本 简单的参数判断,这里只给出一个分支,脚本save.sh内容如下: 此时如果执行脚本时没有携带参数,将会报错: 这个脚本的问题很多

    2024年02月09日
    浏览(46)
  • Python【Matplotlib】交互式时间序列绘图,将x轴设置为日期时间格式并和鼠标拖动缩放相结合

    上篇博客:python【matplotlib】鼠标拖动滚动缩放坐标范围和拖动图例共存,得到启发,我们已经可以通过鼠标拖动缩放坐标范围和移动图例,来实现动态交互式绘图了,对于x轴是时间序列的绘图需求,能否也实现动态交互式绘图呢? 答案是肯定的,接下来我将详细描述其实现

    2024年03月13日
    浏览(56)
  • 使用 htmx 构建交互式 Web 应用

    学习目标:了解htmx的基本概念、特点和用法,并能够运用htmx来创建交互式的Web应用程序。 学习内容: 1. 什么是htmx?    - htmx是一种用于构建交互式Web应用程序的JavaScript库。    - 它通过将HTML扩展为一种声明性的交互式语言,使得开发人员可以使用简单的HTML标记来实现动态

    2024年02月10日
    浏览(51)
  • 使用Gradio库创建交互式散点图

    ❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️ 👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博相关......)👈 博主原文链接:https://www.yourmetaverse.cn/nlp/424/ (封面图由文心一格生成)

    2024年02月16日
    浏览(50)
  • TransformControls 是 Three.js 中的一个类,用于在网页中进行 3D 场景中物体的交互式操作。

    demo案例 TransformControls 是 Three.js 中的一个类,用于在网页中进行 3D 场景中物体的交互式操作。让我们来详细讲解它的输入参数、输出、属性和方法: 输入参数: TransformControls 构造函数通常接受两个参数: camera (THREE.Camera):用于渲染场景的摄像机。这个参数是必需的。

    2024年04月15日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包