0

0

深入理解JavaScript缓动函数:精确时间管理与动画实现

DDD

DDD

发布时间:2025-10-29 15:27:01

|

593人浏览过

|

来源于php中文网

原创

深入理解JavaScript缓动函数:精确时间管理与动画实现

本文旨在深入探讨javascript中缓动函数(easing functions)的正确使用方法,解决动画时间管理中的常见问题。文章将重点阐述如何通过精确追踪动画的起始时间,并结合`requestanimationframe`,实现平滑、可控且从预期值开始的动画效果,避免因时间戳误用导致的动画异常。

引言:缓动函数及其在动画中的作用

在Web前端开发中,缓动函数是实现非线性动画效果的关键工具。它们允许动画在开始、中间或结束时加速或减速,从而使视觉体验更加自然和富有表现力。一个典型的缓动函数通常接受四个参数:

  • t (time):当前时间,即动画已进行的时间。
  • b (begin):动画属性的起始值。
  • c (change):动画属性的总变化量(目标值 - 起始值)。
  • d (duration):动画的总持续时间。

通过这些参数,缓动函数计算出在给定当前时间t时,动画属性应达到的精确值。

常见陷阱:时间戳误用

许多开发者在初次使用缓动函数时,可能会遇到一个普遍的问题:动画无法从预期的起始值(例如0)开始,而是跳到一个较大的值。这通常是由于对时间戳的误解和不当使用造成的。

例如,如果一个函数像下面这样尝试获取时间:

立即学习Java免费学习笔记(深入)”;

getEaseTimeVar() {
    var timeStamp = performance.now();
    this.#secondsPassed = (timeStamp - this.#oldTimeStamp) / 1000;
    this.#oldTimeStamp = timeStamp;
    return timeStamp;
}

这个函数返回的是自页面加载以来(或performance.now()的参考点)的总时间。当动画在代码执行的早期启动时,t值相对较小,动画行为可能符合预期。然而,如果动画在页面加载后经过一段时间(例如5秒)才被触发,此时timeStamp已经是一个很大的值。如果直接将这个值作为缓动函数的t参数,动画会立即跳到一个较大的中间值,而不是从其b参数定义的起始值开始。

问题的核心在于,缓动函数中的t参数需要的是动画从开始至今的已逝时间,而不是程序运行的总时间。

核心策略:精确追踪动画起始时间

要正确使用缓动函数,关键在于为每个独立的动画实例维护其自身的起始时间。当一个动画被触发时,我们需要记录下那一刻的精确时间作为该动画的startTime。之后,在动画的每一帧中,我们通过当前时间减去startTime来计算出动画的已逝时间animTime。

// 动画启动时
let startTime = performance.now();
// 在动画循环中
let animTime = performance.now() - startTime;

只有当animTime介于0和animDuration之间时,动画才应该进行。一旦animTime超过animDuration,动画就应该停止,或者将属性值设置为动画的最终目标值,以确保动画的完整性。

码上飞
码上飞

码上飞(CodeFlying) 是一款AI自动化开发平台,通过自然语言描述即可自动生成完整应用程序。

下载

实践案例:使用缓动函数实现动画

以下是一个结合requestAnimationFrame和精确时间管理来使用缓动函数的示例。我们将创建一个画布动画,点击画布时启动一个物体沿不同缓动曲线移动的动画。

// 定义缓动函数(来自 https://spicyyoghurt.com/tools/easing-functions)
// t: current time, b: beginning value, c: change in value, d: duration
const easeLinear = (t, b, c, d) => c * t / d + b;
const easeInOutQuad = (t, b, c, d) => (t /= d * 0.5) < 1 ? c * 0.5 * t * t + b : -c * 0.5 * ((t - 1) * (t - 3) - 1) + b;

// 动画状态变量
let animating = false; // 标记动画是否正在进行
let startTime;         // 动画的起始时间
const animDuration = 2000; // 动画总时长,单位毫秒

// 获取Canvas上下文
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

// 为Canvas添加点击事件监听器,用于启动/重启动画
canvas.addEventListener("click", () => {
    startTime = performance.now(); // 记录动画开始的时间
    // 如果动画未进行,则启动requestAnimationFrame循环
    if (!animating) {
        requestAnimationFrame(mainLoop);
    }
});

/**
 * 主动画循环函数
 * @param {DOMHighResTimeStamp} time - requestAnimationFrame 提供的当前时间戳
 */
function mainLoop(time) {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布

    if (startTime !== undefined) { // 确保动画已启动
        // 计算动画已逝时间
        const animTime = time - startTime;

        // 使用缓动函数计算当前帧的X和Y坐标
        // X轴:使用线性缓动,从-20移动到 canvas.width + 40
        const x = easeLinear(animTime, -20, canvas.width + 40, animDuration);
        // Y轴:使用easeInOutQuad缓动,从20移动到 canvas.height - 40
        const y = easeInOutQuad(animTime, 20, canvas.height - 40, animDuration);

        // 绘制圆形
        ctx.beginPath();
        ctx.arc(x, y, 20, 0, Math.PI * 2);
        ctx.fill();

        // 判断动画是否仍在进行
        if (animTime < animDuration) {
            // 如果动画未结束,继续请求下一帧
            requestAnimationFrame(mainLoop);
            animating = true;
        } else {
            // 动画结束,将animating状态设为false
            animating = false;
            // 可选:确保动画最终位置准确
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.beginPath();
            ctx.arc(
                easeLinear(animDuration, -20, canvas.width + 40, animDuration),
                easeInOutQuad(animDuration, 20, canvas.height - 40, animDuration),
                20, 0, Math.PI * 2
            );
            ctx.fill();
        }
    }
}

对应的HTML和CSS:


点击画布以启动/重启动画。

代码解析:

  1. 缓动函数定义:easeLinear和easeInOutQuad是两个常用的缓动函数,它们根据t, b, c, d计算出当前值。
  2. 动画状态管理:animating布尔值用于防止重复启动requestAnimationFrame循环。startTime记录动画开始的确切时间。animDuration定义动画的总时长。
  3. 事件监听:当用户点击画布时,startTime被更新为当前performance.now()的值,并(如果动画未进行)调用requestAnimationFrame(mainLoop)启动动画循环。
  4. mainLoop函数:这是动画的核心。
    • ctx.clearRect():每帧开始时清除画布,为新绘制做准备。
    • animTime = time - startTime:这是关键所在,它计算的是自当前动画启动以来经过的时间,而不是页面加载以来的总时间。time参数由requestAnimationFrame提供,是高精度时间戳。
    • 缓动函数应用:easeLinear和easeInOutQuad被用于计算圆形在X和Y轴上的位置。注意参数的设置:b是起始位置,c是总移动距离(例如canvas.width + 40 - (-20)),d是animDuration。
    • 动画循环控制:if (animTime 浏览器在下一次重绘时再次调用mainLoop,从而形成连续动画。一旦animTime达到或超过animDuration,动画停止,animating设为false。

缓动函数参数的深入理解

为了更清晰地理解缓动函数的四个参数:

  • t (current time):当前动画已逝时间。这个值从0开始,逐渐增加到d。在我们的示例中,它就是animTime。
  • b (beginning value):动画属性的起始值。例如,一个物体开始移动的X坐标,或一个元素透明度的初始值。
  • c (change in value):动画属性的总变化量。这是目标值减去起始值的结果。例如,如果一个物体从X=0移动到X=100,那么c就是100。如果从X=100移动到X=0,c就是-100。
  • d (duration):动画的总持续时间。这个值通常以毫秒为单位。

理解这些参数对于正确构建和使用缓动函数至关重要。

注意事项与最佳实践

  1. 使用requestAnimationFrame:始终使用requestAnimationFrame来驱动动画。它会同步浏览器重绘周期,确保动画流畅且高效,避免不必要的CPU/GPU消耗。
  2. 精确的时间戳:performance.now()提供高精度时间戳,非常适合计算动画的已逝时间。
  3. 动画结束处理:当animTime达到animDuration时,确保动画属性最终设置为目标值,以避免由于浮点数精度问题或动画提前结束导致的不精确。在示例中,我们在动画结束后重新计算并绘制了最终位置。
  4. 模块化:对于复杂的动画,可以考虑将动画逻辑封装成类或模块,以便更好地管理多个动画实例及其状态。
  5. 避免全局时间依赖:切勿将performance.now()的原始值直接作为缓动函数的t参数,除非该动画是从页面加载那一刻开始的唯一动画。

总结

正确使用JavaScript缓动函数的核心在于对动画时间的精确管理。通过记录每个动画实例的独立startTime,并计算相对于该起始时间的animTime,我们可以确保动画始终从预期的起始值开始,并按照定义的缓动曲线平滑过渡。结合requestAnimationFrame,这种方法能够构建出高性能、视觉效果优秀的Web动画。掌握这一技巧,将使你在开发动态交互界面时更加得心应手。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

778

2023.08.22

html5动画制作有哪些制作方法
html5动画制作有哪些制作方法

html5动画制作方法有使用CSS3动画、使用JavaScript动画库、使用HTML5 Canvas等。想了解更多html5动画制作方法相关内容,可以阅读本专题下面的文章。

511

2023.10.23

php中文乱码如何解决
php中文乱码如何解决

本文整理了php中文乱码如何解决及解决方法,阅读节专题下面的文章了解更多详细内容。

1

2026.01.28

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

1

2026.01.28

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

23

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

120

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

51

2026.01.26

苹果官方查询网站 苹果手机正品激活查询入口
苹果官方查询网站 苹果手机正品激活查询入口

苹果官方查询网站主要通过 checkcoverage.apple.com/cn/zh/ 进行,可用于查询序列号(SN)对应的保修状态、激活日期及技术支持服务。此外,查找丢失设备请使用 iCloud.com/find,购买信息与物流可访问 Apple (中国大陆) 订单状态页面。

192

2026.01.26

npd人格什么意思 npd人格有什么特征
npd人格什么意思 npd人格有什么特征

NPD(Narcissistic Personality Disorder)即自恋型人格障碍,是一种心理健康问题,特点是极度夸大自我重要性、需要过度赞美与关注,同时极度缺乏共情能力,背后常掩藏着低自尊和不安全感,影响人际关系、工作和生活,通常在青少年时期开始显现,需由专业人士诊断。

7

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 0.8万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

CSS教程
CSS教程

共754课时 | 24.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号