• 250张png图片实现动画实现方案事件


    一、文章参考

    1. requestAnimationFrame MDN
    2. CSS pointer-events 属性

    二、需求说明

    最近开发的一个项目中,要求使用一个动画效果,UCD 提供了三种解决方案,各有优劣。

    2.1 使用 SVG 做成动画

    使用 AE插件 lottie-web 生成一个SVG 动画,浏览器渲染SVG,由于DOM频繁的变动,即不断的重绘,这个动作会大量的占用CPU 和 GPU 的资源,针对一些比较老的机器(工厂、政府单位的机器可能比较老旧),可能会导致资源不够,打开浏览器CPU直接冲到100%导致浏览器卡死。

    应用场景:针对特定性能比较强悍的机器,适用于全屏和局部界面的动画

    2.2 使用 视屏或者gif作为 动图背景

    视频或者gif 能很好的展示动画的完整性,资源占用也比较小,但是会有如下缺点:

    1. 最重要的是不透明, 无法和其他界面有效的整合展示
    2. 无法准确的控制视频的进度

    应用场景:可以用于浏览器部分界面的动画

    2.3 使用图片来做成动画

    原理:人的视觉保留,1秒钟切换24张画面,人就感觉是一个动图

    优点:

    1. 图片是透明的,可以作为背景与其他的界面有效结合

    缺点:

    1. 图片的数量比较多,需要一次性加载很多图片(浏览器默认在同一域名下,最多发送6个请求连接),效率低
    2. 下载到硬盘的图片导入到内存也需要一定的时间,如果图片数量多,消耗的时间也很多
    3. 将图片实现动画效果,需要的图片大小 要 远大于视频、SVG、gif 图片
    4. 如果动画频率比较高,即1秒钟需要处理的图片很多,比较耗 GPU 和 CPU资源

    使用场景:可以作为全屏的解决方案

    三、动画解决方案整理

    由于UCD设计的动画是全屏的(要透明效果,放弃了gif和视频),但无法确保用户最终使用的设备是高性能的(放弃了SVG方案),最终只能选择 多张图片快速切换实现的动画效果。

    3.1 requestAnimationFrame

    window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

    3.1.1 代码实现

    在浏览器重绘的时候,修改目标控件的背景图片

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    <style>
      .donghuabg{
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        top: 0;
        background-image: url('./donghua/y_00000.png');
        background-size: cover;
        pointer-events: none;
      }
    style>
    <body>
      <div class="donghuabg" id="donghuabg">div>
    body>
    <script>
      const donghua = document.getElementById('donghuabg')
    
      let num = 0
      let numStr = '000'
      function step() {
        console.log('继续调用' + num);
        if (num < 249) {
          num++
          if (num <= 9) {
            numStr = '00' + num
          } else if (num > 9 && num < 100){
            numStr = '0' + num
          } else {
            numStr = '' + num
          }
    
          donghua.style.backgroundImage = `url('./donghua/y_00${numStr}.png')`
          console.log('继续调用');
          window.requestAnimationFrame(step);
        } else {
          console.log('结束')
        }
      }
    
      window.requestAnimationFrame(step);
    script>
    html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    3.1.2 现象

    浏览器会一直出现白屏现象,一直到动画执行完成,呈现出最后状态的背景图片

    3.1.3 原因分析

    在这里插入图片描述

    1. 普通PC机器显示器是1秒钟刷新60次,即1秒钟会执行60次 requestAnimationFrame 函数
    2. 代码逻辑即 1秒钟会执行60次 切换背景图片,即会下载60张背景图片,然后将60张图片渲染出来,即16.66666 ms(1000 / 60)(毫秒)需要下载完图片并将图片渲染出来,这个依赖于网络和CPU(GPU)资源
    3. 网络保证图片能下载下来,硬件资源(CPU和GPU)保证图片能渲染出来,两个动作需要在16.66666毫秒内完成,由于本机不具备这样的条件和能力,因此图片还没有渲染出来就切换到下一张图片了,导致界面一直处于白屏的状态。

    3.2setTimeout 结合

    由于动画是10秒钟要执行250张动画,即每张图片的间隔是40ms,因此可以尝试使用setTimeout定时处理

    3.2.3 代码实现

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    <style>
      .donghuabg{
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        top: 0;
        background-image: url('./donghua/y_00000.png');
        background-size: cover;
        pointer-events: none;
      }
    style>
    <body>
      <div class="donghuabg" id="donghuabg">div>
    body>
    <script>
      const donghua = document.getElementById('donghuabg')
    
      let num = 0
      let numStr = '000'
      function step() {
        console.log('继续调用' + num);
        if (num < 249) {
          num++
          if (num <= 9) {
            numStr = '00' + num
          } else if (num > 9 && num < 100){
            numStr = '0' + num
          } else {
            numStr = '' + num
          }
    
          donghua.style.backgroundImage = `url('./donghua/y_00${numStr}.png')`
          console.log('继续调用');
          setTimeout(function () {
            step()
          }, 40)
        } else {
          console.log('结束')
        }
      }
    
      setTimeout(function () {
        step()
      }, 40)
    script>
    html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    3.2.3 现象

    屏幕会不团的闪烁,出现“闪屏”现象

    3.2.3 原因分析

    每隔40毫秒再次改变控件的背景,即改变的一瞬间,浏览器需要去从网上下载,图片还没有下载到本地,浏览器本地还没有图片渲染,界面就会白屏

    3.3 requestAnimationFrame 和 setTimeout 结合

    考虑到 requestAnimationFrame 每次都是屏幕刷新的时候才会触发方法,即,图片变化一定是显示器刷新的时候,如果图片每次都能顺利呈现出来,即动画可以顺利完成,因此需要保证刷新的时候,图片是一定下载完成到本地了的。
    由于是10秒钟250张图片,因此40ms才会跳转到下一章,这个时间作为网络下载图片和渲染图片的时间。
    另外,为了节省网络下载的时间,因此决定先下载图片到本地缓存起来,40ms只用来做为渲染的时间,提高CPU的利用率。

    3.2.1 代码实现

    用5秒钟先下载图片,5秒之后,再开始切换图片的逻辑

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    <style>
      .donghuabg{
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        top: 0;
        background-image: url('./donghua/y_00000.png');
        background-size: cover;
        pointer-events: none;
      }
    style>
    <body>
      <div class="donghuabg" id="donghuabg">div>
      <div id="aaa">
        
      div>
    body>
    <script>
    
      function loadImg(){
        let target = ''
        let numStr = ''
        let num = -1
        for (var i = 0; i < 250; i++) {
          num++
          if (num <= 9) {
            numStr = '00' + num
          } else if (num > 9 && num < 100){
            numStr = '0' + num
          } else {
            numStr = '' + num
          }
          target = target + `${numStr}.png" style="display:none" />`
        }
        console.log(target);
        document.getElementById('aaa').innerHTML = target
      }
      loadImg()
    
      setTimeout(function () {
        const donghua = document.getElementById('donghuabg')
        let num = -1
        let numStr = '000'
        let startTimeStamp = 0
        let currentTimeStamp = 0
        function step() {
          console.log('继续调用' + num);
          if (startTimeStamp === 0) { // 表示是第一次开始遍历
            num++
            startTimeStamp = new Date().getTime()
            donghua.style.backgroundImage = `url('./donghua/y_00${numStr}.png')`
            window.requestAnimationFrame(step);
          } else { // 表示不是第一次遍历了
            currentTimeStamp = new Date().getTime()
            console.log((currentTimeStamp - startTimeStamp) > 40);
            if ((currentTimeStamp - startTimeStamp) > 40 ) {
              startTimeStamp = currentTimeStamp
              if (num < 249) {
                num++
                if (num <= 9) {
                  numStr = '00' + num
                } else if (num > 9 && num < 100){
                  numStr = '0' + num
                } else {
                  numStr = '' + num
                }
    
                donghua.style.backgroundImage = `url('./donghua/y_00${numStr}.png')`
                console.log('继续调用');
                window.requestAnimationFrame(step);
              } else {
                console.log('结束')
              }
            } else { // 时间间隔相差不到40毫秒
              window.requestAnimationFrame(step);
            }
          }
        }
    
        window.requestAnimationFrame(step);
      }, 5000)
    
    script>
    html>	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    3.3.2 现象

    使用5秒预先下载图片之后再执行动画脚本:动画执行比较流畅

    不使用5秒下载图片,直接执行动画脚本:浏览器会出现闪屏的情况

    3.3.3 原因分析

    不使用5秒下载图片,直接执行动画脚本:
    由于两张图片之间的时间间隔是40ms,需要同时完成下载和展示,才能保证动画的连贯性。下载的时间要远大于CPU渲染图片的时间,如果下载的时间比较长,而背景已经变化,但是还没有下载下来,就会出现白屏,一旦下载了,就立马渲染出来,整个过程就会出现“白屏-渲染背景-白屏-渲染背景”的往复循环,即“闪动”现象。

    使用5秒下载图片之后再执行动画脚本:
    浏览器提前会下载图片并缓存到本地,执行动画的时候,下载过程就变成了从缓存获取,大大缩短了下载图片的时间,40ms的时间留有足够的时间用于渲染,看上去就比较流畅

    3.4 雪碧图

    雪碧图:将多个图片合并为一张图片
    动画原理:将雪碧图作为div 控件的背景,**然后定时器改变背景图的展示位置,在1秒钟内变换的次数超过24次,**人就会感觉是动画的效果

    优点:

    1. 只用发送一次请求,即获得所有的图片,提高请求的效率
    2. 只需要切换背景的位置,而不用切换图片资源(重新下载图片),只会重绘不会回流,提高效率

    3.4.1 代码实现

    由于有250张图片,如果合成为1张图片,太大了,不确定能否显示出来,而且不方便整合。因此,UCD给了我10张图片合并为1张雪碧图,在雪碧图从第一张切换到最后一张背景,然后切换到下一个雪碧图,依次走到最后一张图的背景切换

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    <style>
      .donghuabg{
        position: absolute;
        left: 0;
        top: 0;
        height: 1080px;
        width: 1920px;
        /* background-image: url('./xuebi/00-09@1x.png'); */
        /* background-size: cover; */
        pointer-events: none;
        /* background-repeat: no-repeat; */
      }
    style>
    <body>
    
      <div class="donghuabg" id="donghuabg">div>
      <div class="aaa" id="aaa">div>
    body>
    <script>
       function loadImg(){
        let target = ''
        let numStr = ''
        let num = -1
        for (var i = 0; i < 5; i++) {
          num++
          // if (num <= 9) {
          //   numStr = '00' + num
          // } else if (num > 9 && num < 100){
          //   numStr = '0' + num
          // } else {
          //   numStr = '' + num
          // }
          target = target + `${num}0-${num}9@1x.png" style="display:none" />`
        }
        console.log(target);
        document.getElementById('aaa').innerHTML = target
      }
      loadImg()
    
      setTimeout(function () {
        const donghua = document.getElementById('donghuabg')
        let num = -1 // 图片的张数
        let bgPositionIndex = -1 // 图片背景的序号
        let numStr = '000'
        let startTimeStamp = 0
        let currentTimeStamp = 0
        function step() {
          console.log('继续调用' + num);
          if (startTimeStamp === 0) { // 表示是第一次开始遍历
            num++
            bgPositionIndex++
            startTimeStamp = new Date().getTime()
            donghua.style.backgroundImage = `url('./xuebi/${num}0-${num}9@1x.png')`
            console.log('bgPositionIndex', bgPositionIndex)
            donghua.style.backgroundPosition = `0px -${1080 * bgPositionIndex}px`
            window.requestAnimationFrame(step);
          } else { // 表示不是第一次遍历了
            currentTimeStamp = new Date().getTime()
            console.log((currentTimeStamp - startTimeStamp) > 40);
            if ((currentTimeStamp - startTimeStamp) > 40 ) {
              startTimeStamp = currentTimeStamp
              if (bgPositionIndex < 9) {
                bgPositionIndex++
                console.log('bgPositionIndex', bgPositionIndex)
                donghua.style.backgroundPosition = `0px -${1080 * bgPositionIndex}px`
                console.log(donghua.style.backgroundPosition)
                window.requestAnimationFrame(step);
              } else {
                if (num < 4) {
                  num++
                  bgPositionIndex = -1
                  donghua.style.backgroundImage = `url('./xuebi/${num}0-${num}9@1x.png')`
                  console.log('bgPositionIndex', bgPositionIndex)
                  donghua.style.backgroundPosition = `0px -${1080 * bgPositionIndex}px`
                  window.requestAnimationFrame(step);
                } else {
                  console.log('结束')
                }
              }
            } else { // 时间间隔相差不到40毫秒
              window.requestAnimationFrame(step);
            }
          }
        }
    
        window.requestAnimationFrame(step);
      }, 5000)
    
    script>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    3.4.2 现象

    在同一张雪碧图中切换背景,还是很流畅的,但是雪碧图切换的一瞬间,能很明显的感受到“动画”的抖动(图片切换很明显)

    3.4.3 原因分析

    1. 雪碧图切换背景,只牵涉到重绘,40ms的时间足够了,动画感受很流畅
    2. 当雪碧图切换的时候,由于图片比较大,(1000 / 60)16ms 需要渲染4M的图片并展示出来,可能CPU 资源不够,出现一瞬间的白屏

    最好的解决办法:将所有的图片合并为一张雪碧图

    3.5 滚动条的展示

    设置一个DIV控件展示一张图片,将所有的图片全部放到该DIV中,隐藏滚动条,定时设定滚动条的位置(一张图片实际高度的整数倍),快速设置滚动条的位置,实现动画的连续性

    3.5.1 代码实现

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    <style>
      .imgContainer{
        height: 1080px;
        width: 1920px;
      }
      .imgContainer img{
        width: 100%;
        height: auto;
      }
    style>
    <body>
      <div class="aaa" id="aaa">div>
    body>
    <script>
       function loadImg(){
        let target = ''
        let numStr = ''
        let num = -1
        for (var i = 0; i < 250; i++) {
          num++
          if (num <= 9) {
            numStr = '00' + num
          } else if (num > 9 && num < 100){
            numStr = '0' + num
          } else {
            numStr = '' + num
          }
          target = target + `
    ${numStr}.png"/>
    `
    } console.log(target); document.getElementById('aaa').innerHTML = target } loadImg() setTimeout(function () { let num = -1 // 图片的张数 var scroll = document.getElementById('aaa').scrollTop function donghua () { num++ document.documentElement.scrollTop = 1080 * num window.requestAnimationFrame(donghua); } window.requestAnimationFrame(donghua); }, 2000)
    script> html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    3.5.1 现象

    动画很流畅,没有出现跳帧、抖动、白屏的情况

    3.5.1 原因分析

    所有的图片全部都下载到本地了,并且也在内存中了,不存在下载和加载图片这两个过程,只有渲染,效率是最高的。

    四、动画穿透 pointer-events

    如果全屏动画放在最上层就会阻止用户点击到其他组件,最好希望这个动画像“透明”一样的存在,只是展示不监听任何事件,因此 CSS pointer-events 属性就是最好的选择

    4.1 语法说明

    pointer-events: auto|none;
    
    • 1

    属性值:

    auto默认值,设置该属性链接可以正常点击访问。
    none元素不能对鼠标事件做出反应

    4.2 使用场景

    4.2.1 按钮的防重点击

    原理说明:在点击某个按钮之后,立马让按钮的属性设置为pointer-events: none;这样按钮就可以忽略所有的点击事件了;然后再设置一个定时器,比如过3秒之后再回复到原来的状态 pointer-events: auto

    Vue.directive("preventReClick", {
      inserted(el, binding) {
        el.addEventListener("click", () => {
          if (el.style.pointerEvents !== "none") {
            el.style.pointerEvents = "none"
            setTimeout(() => {
              el.style.pointerEvents = ""
            }, 2000)
          }
        })
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.2.2 界面水印效果

    原理说明:水印只是用于显示,并不希望被用户鼠标选中和参与点击事件,因此可以将其设置为“透明”

  • 相关阅读:
    【RocketMQ中生产者生产消息的高可用机制、消费者消费消息的高可用机制、消息的重试机制、死信队列于死信消息】
    csv和excel文件操作
    解读 | 自动驾驶系统中的多视点三维目标检测网络
    在Linux系统中搜索当前路径及其子目录下所有PDF文件中是否包含特定字符串
    计算机网络第4章-IPv6和寻址
    一文了解蛋白功能结构域预测与分析
    Selenium之Webdriver驱动大全【Firefox、Chrome、IE、Edge、Opera、PhantomJS】
    预告|年度总决赛即将打响, 20余个项目角逐嘉兴经开区
    题目 1064: 二级C语言-阶乘数列
    【python】使用pysam读取sam文件时的常用属性
  • 原文地址:https://blog.csdn.net/hbiao68/article/details/126724811