只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上)

这篇具有很好参考价值的文章主要介绍了只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

settimeout/setinterval

requestAnimationFrame

基本用法

时间戳参数

帧数与时间戳计算

自动暂停

JS中的贝塞尔曲线

概念

公式

二次贝塞尔

三次贝塞尔

N次贝塞尔

贝塞尔曲线+动画

动画类

在动画中使用贝塞尔

总结

相关代码:

贝塞尔曲线相关网站:

参考文章:


前言

上篇文章我们详细的讲述了CSS中的原生动画技术,了解了过渡与动画属性。那么本文将与大家分享原生JS中的动画方案,有兴趣的同学请接着往下看

JS实现动画的形式有定时器,动画帧以及动画API技术

settimeout/setinterval

早期的JS中动画帧和动画API的概念尚不存在,开发者通常使用定时器生成对应动画

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
    <style>
      html,
      body {
        width: 100%;
        height: 100%;
        position: relative;
      }
      .box {
        width: 200px;
        height: 200px;
        top: 50px;
        position: absolute;
        background: lightblue;
      }
    </style>
  </head>
  <body>
    <div class="box"></div>
    <button id="play_pause">暂停/继续</button>
    <button id="direct">改变方向</button>
    <script>
      class MyAnimate {
        // 计时器id
        timer = null;
        // 创建计时器,动画开始
        startAnimate(fn, frame = 60) {
          // frame-帧数,默认60帧
          const { timer } = this;
          if (timer) {
            this.stopAnimate();
          }
          this.timer = setInterval(fn, 1000 / frame);
        }
        // 销毁计时器,动画结束
        stopAnimate() {
          const { timer } = this;
          if (timer) {
            clearInterval(timer);
            this.timer = null;
          }
        }
      }
      let speed = 3;
      const box = document.querySelector(".box");
      const play_pause_btn = document.querySelector("#play_pause");
      const direct_btn = document.querySelector("#direct");
      const animate = new MyAnimate();
      play_pause_btn.addEventListener("click", playPauseHandler);
      direct_btn.addEventListener("click", directHandler);
      // 动画开始、停止
      function playPauseHandler() {
        if (animate.timer) return animate.stopAnimate();
        animate.startAnimate(mainAnimate);
      }
      // 动画方向修改
      function directHandler() {
        speed = -speed;
      }
      // 尽量保持一个动画函数
      function mainAnimate() {
        moveBox(speed);
      }
      function moveBox(speed = 1) {
        const { style, offsetLeft } = box;
        style.left = `${offsetLeft + speed}px`;
      }
    </script>
  </body>
</html>

我使用上面的代码实现了一个简单的匀速运动,其中按钮一控制运动和暂停,按钮二控制运动的方向,其中speed用于控制速度,frame控制帧数,下面是动画效果

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

requestAnimationFrame

requestAnimationFrame早在2011就由W3C提出了,它是一种用于在浏览器中执行动画效果的方法,我们在前文讲述事件循环时提到过,它属于宏任务的一种,但是在settimeout之前执行,在执行完微任务后,会执行requestAnimationFrame调度动画帧,然后浏览器会进行重绘和重排。相对于定时器,这样做可以使requestAnimationFrame中的dom操作得到优化,提高性能

基本用法

我们使用动画帧的方式来实现一下上面的效果

let speed = 3;
let animate = null;
const box = document.querySelector(".box");
const play_pause_btn = document.querySelector("#play_pause");
const direct_btn = document.querySelector("#direct");
play_pause_btn.addEventListener("click", playPauseHandler);
direct_btn.addEventListener("click", directHandler);

// 动画开始、停止
function playPauseHandler() {
    if (!!animate) {
        cancelAnimationFrame(animate)
        return animate = null
    }
    mainAnimate();
}
// 动画方向修改
function directHandler() {
    speed = -speed;
}
// 尽量保持一个动画函数
function mainAnimate() {
    moveBox(speed)
    animate = requestAnimationFrame(mainAnimate);
}
function moveBox(speed = 1) {
    const { style, offsetLeft } = box;
    style.left = `${offsetLeft + speed}px`;
}

时间戳参数

requestAnimationFrame的钩子函数中会回传一个参数,这个参数是当前页面运行的时间戳,它是相对于页面加载的某个参考点来计算的,因此第一次的值会大于0。

这个时间戳是什么?

浏览器提供了一个性能监控的api:performance,其中performance.now()函数会返回当前时间戳,这个时间戳是以页面打开时间(performance.timing.navigationStart)为起点的

我们将浏览器的生命周期看成是一条时间轴,里面有许多时间节点,当执行requestAnimationFrame时相当于进入了时间轴的某个时间点,每一段时间会调用一个回调函数,当然当运行cancelAnimationFrame时相当于跳出了这个时间轴,当下次再进入时间轴时接着调用回调,流程可以参考下图

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

运行下面的代码可以打印出动画帧的时间戳

<!DOCTYPE html>
<html lang="CH">

<head>
    <meta charset="UTF-8" />
    <title></title>
</head>

<body>
    <button onclick="playPauseHandler()">暂停/继续</button>
    <script>
        let animate
        // 动画开始、停止
        function playPauseHandler() {
            if (animate) {
                cancelAnimationFrame(animate)
                return animate = null
            }
            startAnimate()
        }
        // 尽量保持一个动画函数
        function mainAnimate(timeStamp) {
            console.log(timeStamp);
            startAnimate()
        }
        function startAnimate() {
            animate = requestAnimationFrame(mainAnimate);
        }
    </script>
</body>

</html>

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

帧数与时间戳计算

基于上面的知识点和代码,我们可以利用performance和动画帧实现一个动画计时和帧数计算,请思考下面的代码

let animate, temp = 0, firstStamp = 0;
// 动画开始、停止
function playPauseHandler() {
    if (animate) {
        cancelAnimationFrame(animate)
        return animate = null
    }
    firstStamp = performance.now()// 重置时间戳
    startAnimate()
}
function mainAnimate(timeStamp = 0) {
    console.info(`FPS(每秒帧数):${1000 / (timeStamp - temp)}`)
    console.info(`timeStamp(时间戳):${timeStamp - firstStamp}`)
    startAnimate()
}
function startAnimate() {
    temp = performance.now()// 通过时间差计算帧数
    animate = requestAnimationFrame(mainAnimate);
}

效果如下

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

自动暂停

基于上面的知识点,我们还可以将其应用到实际场景中,如果有一个动画需要限制其运行时间,比如一秒(限制时间可以更好的使用贝塞尔曲线),那么我们可以参考下面的代码

let animate, firstStamp = 0;
const box = document.querySelector(".box"),
    totalStamp = 1000//动画执行一秒
// 动画开始、停止
function playPauseHandler() {
    if (animate) {
        cancelAnimationFrame(animate)
        return animate = null
    }
    firstStamp = performance.now()
    startAnimate()
}
function mainAnimate(timeStamp = 0) {
    if (totalStamp >= (timeStamp - firstStamp)) {// 当超出限定的帧数时,暂停动画
        moveBox()
    }
    startAnimate()
}
function startAnimate() {
    animate = requestAnimationFrame(mainAnimate);
}
function moveBox(speed = 3) {
    const { style, offsetLeft } = box;
    style.left = `${offsetLeft + speed}px`;
}

效果如下: 

 只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

我们在每一次点击开始动画都会将firstStamp重置,以保证动画的周期始终是一秒

JS中的贝塞尔曲线

上述的动画使用的运动方式都是匀速运动,为了让动画更平滑,提升视觉观感,我们同样可以在JS中使用贝塞尔曲线

概念

在高中和大学,我们可能接触到了导数,也大致了解过速度与加速度的关系,贝塞尔曲线的变化是线性的,为了保证动画的可预测和可控性,贝塞尔曲线长度通常是有限的

下面两张图分别是使用德卡斯特里奥算法生成的二次和三次贝塞尔曲线的动画,动画来源于贝塞尔曲线

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

公式

下面是贝塞尔曲线的JS实现方式,我们假设起始点坐标是0,0,结束坐标是1,1。

二次贝塞尔

p0和p2坐标是(0,0)和(1,1)

// 假设起始点坐标是0,0,结束坐标是1,1。
// 二次贝塞尔
function quadraticBezier(_x, _y, t) {
    const mt = 1 - t;
    const t2 = t * t;
    const x = 2 * mt * t * _x + t2;
    const y = 2 * mt * t * _y + t2;
    return [x, y];
}
const point1 = quadraticBezier(1, 0, .5)// 当p1在(1,0)时,若生成的贝塞尔曲线的长度为l,那么t=0.5就表示0.5*l,此时这个点在曲线上的坐标为[0.75, 0.25]
console.log(point1);// [0.75, 0.25]

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划 

三次贝塞尔

与二次类似,假设初始点和末尾点固定为(0,0)和(1,1)

// 三次贝塞尔
function cubicBezier(_x1, _y1, _x2, _y2, t) {
    const cx = 3 * _x1;
    const cy = 3 * _y1;
    const bx = 3 * (_x2 - _x1) - cx;
    const by = 3 * (_y2 - _y1) - cy;
    const ax = 1 - cx - bx;
    const ay = 1 - cy - by;
    const x = (ax * Math.pow(t, 3)) + (bx * Math.pow(t, 2)) + (cx * t);
    const y = (ay * Math.pow(t, 3)) + (by * Math.pow(t, 2)) + (cy * t);
    return [x, y];
}
const point2 = cubicBezier(0, 1, 1, 0, .5)// 与二次同理,t在曲线上的坐标为[0.5, 0.5]
console.log(point2);

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

N次贝塞尔

通过上述的推导,我们可以总结出一个N次贝塞尔的函数

// 计算阶乘
function factorial(n) {
  if (n === 0 || n === 1) {
    return 1;
  }
  return n * factorial(n - 1);
}
// 计算组合数
function combination(n, k) {
  return factorial(n) / (factorial(k) * factorial(n - k));
}
// N次贝塞尔,x和y坐标使用二阶数组传递
function NBezier(points, t) {
  const n = points.length - 1;
  const result = [0, 0];
  for (let i = 0; i <= n; i++) {
    const coefficient =
      combination(n, i) * Math.pow(1 - t, n - i) * Math.pow(t, i);
    result[0] += coefficient * points[i][0];
    result[1] += coefficient * points[i][1];
  }
  return result;
}
const pointN = NBezier(
  [
    [0, 1],
    [0.3, 0.4],
    [1, 0],
  ],
  0.5
);
console.log(pointN); //  [0.4, 0.45]

贝塞尔曲线+动画

动画类

将贝塞尔引入JS之前,我们先基于上面的动画帧实现一个动画类便于后续的使用

class Animate {
  id = null; // 动画索引
  duration = Infinity; // 帧数控制
  isActive = false; // 激活控制
  constructor(fn) {
    this.fn = fn;
  }
  start(duration) {
    if (this.isActive) return;
    this.duration = duration ?? Infinity;
    this.isActive = true;
    this.animate();
  }
  stop() {
    this.isActive = false;
    cancelAnimationFrame(this.id);
  }
  animate(timer) {
    if (this.isActive && this.duration-- > 0) {
      this.fn(timer);
      this.id = requestAnimationFrame(this.animate.bind(this));
    }
  }
}
const box = document.querySelector(".box")
const animate = new Animate(() => {
    moveBox(3)
})
// 动画开始、停止
function playPauseHandler() {
    if (animate.isActive) {
        return animate.stop()
    }
    animate.start()
}
function moveBox(speed = 3) {
    const { style, offsetLeft } = box;
    style.left = `${offsetLeft + speed}px`;
}

在动画中使用贝塞尔

接着,我们将贝塞尔的函数代入到动画帧中,使用我们上面说到的贝塞尔公式和动画帧可以实现JS动画缓动的效果

<!DOCTYPE html>
<html lang="CH">

<head>
    <meta charset="UTF-8" />
    <title>cubicBezier</title>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            position: relative;
        }

        .box {
            width: 200px;
            height: 200px;
            top: 50px;
            position: absolute;
            background: lightblue;
        }
    </style>
</head>

<body>
    <div class="box"></div>
    <button onclick="playPauseHandler()">play</button>
    <script src="./animate.js"></script>
    <script src="./bezier.js"></script>
    <script>
        const box = document.querySelector(".box")
        let startTime
        const limit = 1000// 限制动画时间1000毫秒
        const speed = 100// 动画跨度
        const animate = new Animate((t) => {
            const progress = t - startTime// 当前动画相对起始时间的时间戳
            const percentage = progress / limit// 动画进度百分比,相当于贝塞尔曲线的t参数
            if (progress <= limit) {
                const acceleration = getAcceleration(percentage)
                moveBox(acceleration * speed)
            }
        })

        // 动画开始
        function playPauseHandler() {
            startTime = performance.now()// 初始化动画起始时间
            animate.start()
        }
        function moveBox(speed = 3) {
            const { style } = box;
            style.left = `${speed}px`;
        }
        // 获取贝塞尔加速度变化,progress表示时间轴进度
        function getAcceleration(progress) {
            const res = cubicBezier(0, 2, 1, -1, progress)
            return res[1]
        }
    </script>
</body>

</html>

上述代码的效果如下

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

CSS的效果如下 

只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上),JavaScript,面试文档,前端动画,javascript,前端,开发语言,动画,面试,原力计划

总结

本文主要介绍了两种JS实现动画的方式,分别是使用定时器和动画帧,其中动画帧的使用效果要优于定时器,它是在重绘和重排之前运行,节省了性能;接着我们详细了解了贝塞尔曲线的实现以及应用,在动画帧中使用了贝塞尔曲线,用于实现缓动效果

以上就是文章全部内容了,谢谢你看到最后,希望文章对你有帮助,如果觉得文章不错的话,还望三连支持一下博主,感谢!

相关代码:

myCode: 基于js的一些小案例或者项目 - Gitee.com

贝塞尔曲线相关网站:

cubic-bezier.com

贝塞尔曲线在线绘制🚀

参考文章:

贝塞尔曲线

Bézier_curve文章来源地址https://www.toymoban.com/news/detail-572814.html

到了这里,关于只会用插件可不行,这些前端动画技术同样值得收藏-JavaScript篇(上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 你知道HashMap有几种吗?不要只会用最简单的奥!

    这秋意是越来越近了,这思念就开始泛滥… 在 Java 中,有多种哈希映射(HashMap)的实现,每种都有不同的特点和适用场景。以下是几种常见的哈希映射实现: HashMap : 介绍 : HashMap 是 Java 标准库中的哈希映射实现。它使用链地址法(chaining)来解决哈希冲突,并在大多数情

    2024年02月07日
    浏览(37)
  • 别再只会使用简单的ping命令了,Linux中这些高级ping命令可以提高工作效率!

    当你需要测试网络连接或者诊断网络问题时,ping命令是一个非常有用的工具。除了基本的用法,ping还有一些高级用法,可以帮助你更好地使用它。 首先,让我们回顾一下ping的基本用法。ping命令用于测试与另一台计算机的连接是否正常。以下是基本的ping命令: 其中, host

    2023年04月18日
    浏览(59)
  • 这些开源自动化测试框架,会用等于白嫖一个w

    作者:黑马测试 链接:https://www.zhihu.com/question/19923336/answer/2585952461 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。   随着计算机技术人员的大量增加,通过编写代码来进行测试成为一种更为高效的测试方式,由此而诞生了以计算机语

    2024年02月02日
    浏览(52)
  • 前端使用jsencrypt实现RSA公钥解密——uniapp同样适用

    在node_modules目录下,根据如下路径找到rsa.js文件 jsencrypt/lib/lib/jsbn/rsa.js 1、修改 RSAKey.prototype.decrypt 方法(将doPrivate改为doPublic) 2、修改 rsa.js文件下的pkcs1unpad2方法 3、保存文件即可 保存修改后的rsa.js文件,一般情况下不需要重新编译也可生效,如有问题就重新build或serve一下

    2024年02月06日
    浏览(52)
  • 3D建模完成以后,如何用编程语言控制这些模型的展示和动画

    完成 3D 建模后,需要使用一些图形编程库来控制模型的展示和动画。下面是一些常用的图形编程库: OpenGL:OpenGL 是一个跨平台的图形编程接口,可以使用多种编程语言进行开发,比如 C/C++,Python,Java 等。OpenGL 提供了强大的图形渲染能力,可以用来绘制各种 2D 和 3D 图形,

    2024年02月04日
    浏览(33)
  • 如果让你实现实时消息推送你会用什么技术?轮询、websocket还是sse

    在日常的开发中,我们经常能碰见服务端需要主动推送给客户端数据的业务场景,比如_数据大屏的实时数据_,比如_消息中心的未读消息_,比如_聊天功能_等等。 本文主要介绍SSE的使用场景和如何使用SSE。 学习就完事了 我们常规实现这些需求的方案有以下三种 轮询 websock

    2024年03月19日
    浏览(47)
  • 一个合格(优秀)的前端都应该阅读这些文章

    的确,有些标题党了。起因是微信群里,有哥们问我,你是怎么学习前端的呢?能不能共享一下学习方法。一句话也挺触动我的,我真的不算是什么大佬,对于学习前端知识,我也不能说是掌握了什么捷径。当然,我个人的学习方法这篇文章已经在写了,预计这周末会在我个

    2024年02月08日
    浏览(44)
  • 一个合格(优秀)的前端都应该阅读这些文章(coding)

    原文地址:Nealyang/PersonalBlog 的确,有些标题党了。起因是微信群里,有哥们问我,你是怎么学习前端的呢?能不能共享一下学习方法。一句话也挺触动我的,我真的不算是什么大佬,对于学习前端知识,我也不能说是掌握了什么捷径。当然,我个人的学习方法这篇文章已经在

    2024年02月08日
    浏览(38)
  • 【前端修炼场】 — 这些标签你学会了么?快速拿下 “hr”

    此文为【前端修炼场】第四篇,上一篇文章链接: 上一篇 在此之前,诸位道友已经接触不少的标识符了,并且对于VSCode肯定也有诸多的不满!你是否也疑惑,为什么在VSCode里面换行或者空格都不会体现在网页上,这也太不方便了,其实还有更不方便的,我们许多特殊符号也

    2024年01月24日
    浏览(53)
  • 自学web前端觉得好难,可能你遇到了这些困境

    好多人跟我说上学的时候也学过前端,毕业了想从事web前端开发的工作,但自学起来好难,快要放弃了,所以我总结了一些大家遇到的困境,希望对你会有所帮助。   目录 1.  意志是否坚定 2. 没有找到合适自己的老师 3. 为了找到工作,啥都想学  4. 学习途中遇到问题怎么办

    2024年02月02日
    浏览(104)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包