此文章为原创,请勿转载
1.svg实现
2.canvas实现
3.坑点

查阅svg文档后发现,svg动画运动有两种实现方式,且都非常简单,但对于100%实现设计师给出的效果有很大的距离

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>offset-path/offset-roate</title>
  5. </head>
  6. <style type="text/css">
  7. * {
  8. padding: 0;
  9. margin: 0;
  10. box-sizing: border-box;
  11. }
  12. body {
  13. background: #000;
  14. }
  15. .line {
  16. width: 80px;
  17. height: 3px;
  18. position: absolute;
  19. background: red;
  20. offset-path: path("M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500");
  21. animation: move 10s linear infinite;
  22. }
  23. @keyframes move {
  24. 100% {
  25. offset-distance: 2000px;
  26. }
  27. }
  28. .line1 {
  29. position: absolute;
  30. left: 100px;
  31. width: 20px;
  32. height: 20px;
  33. border-radius: 50%;
  34. background: red;
  35. offset-path: path("M0,0a72.5,72.5 0 1,0 145,0a72.5,72.5 0 1,0 -145,0");
  36. offset-rotate: 0deg;
  37. animation: load 1.8s cubic-bezier(0.86, 0, 0.07, 1) infinite;
  38. animation-delay: 0.147s;
  39. animation-fill-mode: forwards;
  40. }
  41. @keyframes load {
  42. from {
  43. offset-distance: 0;
  44. }
  45. to {
  46. offset-distance: 100%;
  47. }
  48. }
  49. </style>
  50. <body>
  51. <h2>路径偏移</h2>
  52. <div class="line"></div>
  53. <svg width="100%" height="600px" version="1.0" id="svg1">
  54. <path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF"></path>
  55. </svg>
  56. <h2>角度偏移</h2>
  57. <div class="line1">
  58. </div>
  59. </body>
  60. </html>

此种方式的限制是滚动元素无法随路径进行没有规律的变化

stroke-dasharray:设置shap和text 边框虚线的实线长度与实线之间的间隔(虚线长度)
stroke-dashoffser:设置边框线条相对于默认位置的偏移(正值:向左,负值:向右)

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>stroke-dasharray/stroke-dashoffser</title>
  5. </head>
  6. <style type="text/css">
  7. * {
  8. padding: 0;
  9. margin: 0;
  10. box-sizing: border-box;
  11. }
  12. body {
  13. background: #000;
  14. color: #fff;
  15. }
  16. .move {
  17. animation: moving 5s infinite;
  18. }
  19. @keyframes moving {
  20. 0% {
  21. stroke-dashoffset: 80px;
  22. }
  23. 100% {
  24. stroke-dashoffset: -1600px;
  25. }
  26. }
  27. </style>
  28. <body>
  29. <h2>设置stroke-dasharray</h2>
  30. <b>storke-dasharray设置为80 ,此时实线和实线间隔一样</b>
  31. <svg width="100%" height="600px" version="1.0" id="svg1">
  32. <path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF" stroke-dasharray="80"></path>
  33. </svg>
  34. <b>storke-dasharray设置为80 320,此时实线和是实线间隔的1/4</b>
  35. <svg width="100%" height="600px" version="1.0" id="svg1">
  36. <path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF" stroke-dasharray="80 320"></path>
  37. </svg>
  38. <h2>设置stroke-dashoffset让边线相对于初始位置发生偏移</h2>
  39. <svg width="100%" height="600px" version="1.0" id="svg1">
  40. <path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF" stroke-dasharray="80 320" stroke-dashoffset="40"></path>
  41. </svg>
  42. <h2>通过设置stroke-dasharray 和 stroke-dashoffset让边线动起来</h2>
  43. <svg width="100%" height="600px" version="1.0" id="svg1">
  44. <path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="#FFF"></path>
  45. <path d="M10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500" fill="#tranparent" stroke="red" stroke-dasharray="80 1600" stroke-dashoffset="0" class="move"></path>
  46. </svg>
  47. </body>
  48. </html>

此种方式通过边框偏移的效果可以设置跟随路径的滚线条,但是无法设置线条的光线效果,即实线的阴影和实线的渐变效果(渐变区域需随着偏移路径的变化而变化)

对于不规则路径,如果直接用画线条的方式实现光线,需要计算每一个开始点和结束点的位置,中间还可能存在转折点,计算起来非常麻烦,不可取
故这边采取canvas组合图形的模式,取线条和一个图形重叠部分(类似于灯罩)来实现光线效果
组合前

组合后

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>canvas实现不规则路径光效</title>
  5. </head>
  6. <style type="text/css">
  7. body {
  8. background: #000;
  9. }
  10. #wrap {
  11. position: absolute;
  12. width: 1200px;
  13. height: 600px
  14. }
  15. </style>
  16. <body>
  17. <div id="wrap">
  18. <canvas id="canvas" width="1200" height="600"></canvas>
  19. </div>
  20. </body>
  21. <script type="text/javascript">
  22. var path = 'M 10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500';
  23. var list = path.match(/([A-Z]([^A-Z]){1,})/g).map(item => {
  24. return {
  25. x: item.split(' ')[1],
  26. y: item.split(' ')[2],
  27. action: item.split(' ')[0],
  28. }
  29. });//获取每个点位置
  30. var canvas = document.getElementById('canvas');
  31. var ctx = canvas.getContext('2d');
  32. ctx.strokeStyle = 'rgba(255,255,255,1)';
  33. function drawPath() {
  34. ctx.lineWidth = 3;
  35. ctx.beginPath();
  36. list.forEach(item => {
  37. if(item.action == 'M') ctx.moveTo(item.x, item.y);
  38. if(item.action == 'L') ctx.lineTo(item.x, item.y);
  39. });
  40. ctx.stroke();
  41. }
  42. drawPath();
  43. function drawLine() {
  44. //设置图形组合方式 默认source-over
  45. ctx.globalCompositeOperation = "destination-in";
  46. ctx.lineWidth = 60;
  47. ctx.beginPath();
  48. ctx.moveTo(40, 80);
  49. ctx.lineTo(200, 80);
  50. ctx.stroke();
  51. }
  52. drawLine();
  53. </script>
  54. </html>

当我们实现好线条剩下就需要让线条动起来,由于线条是通过灯罩的方式来实现的,让线条运动只需要让灯罩动起来就好

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>canvas实现不规则路径光效</title>
  5. </head>
  6. <style type="text/css">
  7. body {
  8. background: #000;
  9. }
  10. #wrap {
  11. position: absolute;
  12. width: 1200px;
  13. height: 600px
  14. }
  15. </style>
  16. <body>
  17. <div id="wrap">
  18. <canvas id="canvas" width="1200" height="600"></canvas>
  19. </div>
  20. </body>
  21. <script type="text/javascript">
  22. var path = 'M 10 80 L 77.5 60 L 145 80 L 280 100 L 500 80 L 600 120 L 800 80 L 950 120 L 950 200 L 930 250 L 950 300 L 950 500';
  23. var list = path.match(/([A-Z]([^A-Z]){1,})/g).map(item => {
  24. return {
  25. x: item.split(' ')[1],
  26. y: item.split(' ')[2],
  27. action: item.split(' ')[0],
  28. }
  29. });//获取每个点位置
  30. var step = 3;
  31. var x1, x2, y1, y2;//确定路径中最大最小点
  32. var timer;
  33. var canvas = document.getElementById('canvas');
  34. var ctx = canvas.getContext('2d');
  35. ctx.strokeStyle = 'rgba(255,255,255,1)';
  36. ctx.shadowColor = 'rgba(255,255,255,1)';
  37. ctx.lineCap = 'round';
  38. ctx.shadowBlur = 3;
  39. list.forEach(item => {
  40. x1 = !x1 || Number(item.x) < x1 ? Number(item.x) : x1;
  41. y1 = !y1 || Number(item.y) < y1 ? Number(item.y) : y1;
  42. x2 = !x2 || Number(item.x) > x2 ? Number(item.x) : x2;
  43. y2 = !y2 || Number(item.y) > y2 ? Number(item.y) : y2;
  44. });
  45. function drawPath() {
  46. ctx.lineWidth = 3;
  47. ctx.beginPath();
  48. list.forEach(item => {
  49. if(item.action == 'M') ctx.moveTo(item.x, item.y);
  50. if(item.action == 'L') ctx.lineTo(item.x, item.y);
  51. });
  52. //添加光效渐变
  53. var grd = ctx.createLinearGradient(arrLine[arrLine.length - 1].x, arrLine[arrLine.length - 1].y, arrLine[0].x, arrLine[0].y);
  54. grd.addColorStop(0, 'rgba(255, 255, 255, 0)'); //定义渐变线起点颜色
  55. grd.addColorStop(1, 'rgba(255, 255, 255, 1)'); //定义渐变线结束点的颜色
  56. ctx.strokeStyle = grd;
  57. ctx.stroke();
  58. }
  59. //设计合适的初始线条状态
  60. var arrLine = Array(10).fill(0).map((item, inx) => {
  61. return {
  62. x: x1 - 20 * inx,
  63. y: y1 + 30,
  64. }
  65. });
  66. //随时间变化图形路径
  67. function getArrLine() {
  68. var isEnd
  69. arrLine = arrLine.map(item => {
  70. var x = item.x;
  71. var y = item.y;
  72. if(x < x2 - 30) {
  73. x = x + step > x2 -30 ? x2 - 30 : x + step;
  74. } else if(x == x2 -30 && y < y2) {
  75. y = y + step > y2 ? y2 : y + step;
  76. } else {
  77. isEnd = true;
  78. }
  79. return {
  80. x,
  81. y
  82. }
  83. });
  84. isEnd && timer && cancelAnimationFrame(timer);
  85. }
  86. //绘制图形
  87. function drawLine() {
  88. //设置图形组合方式 默认source-over
  89. ctx.globalCompositeOperation = "destination-in";
  90. ctx.lineWidth = 70;
  91. ctx.beginPath();
  92. arrLine.forEach((item, inx) => {
  93. if(inx == 0) {
  94. ctx.moveTo(item.x, item.y);
  95. } else {
  96. ctx.lineTo(item.x, item.y);
  97. }
  98. })
  99. ctx.stroke();
  100. }
  101. function start() {
  102. ctx.clearRect(0, 0, 1200, 600);
  103. ctx.globalCompositeOperation = 'source-over';
  104. drawPath();
  105. drawLine();
  106. getArrLine();
  107. timer = requestAnimationFrame(start);
  108. }
  109. timer = requestAnimationFrame(start);
  110. </script>
  111. </html>

这种实现方式也有一定的条件限制,那就是路径可大体抽象成为一个有一定规律的图型或者线条,比如上面demo中路径可抽象成为一个矩形的两边,或者是2条连接的直线
我们必须从没有具体规则的路径中抽象出一个大体的规则,不同路径规则不同
上面的例子就是将不规则路径抽象成了一个直角的规则路径

这边找到了2个可优化的点
1.时间方向上: 为了让动画消耗较小,代码中的定时器已经用的是requestAnimationFrame, 但是由于光线的特殊性(自带模糊效果),为了性能更加,尝试了2次requestAnimationFrame调用一次绘图的方式,效果较前者未有明显区别
2.绘图方向上: 从上图可发现,灯罩每次只圈出路径的一部分,故绘图中不需要每次都绘制全部路径,只需要找出灯罩前后的路径点,将这一段路径绘制出来就好

在完成这个动动画效果之后遇到一个至今原因不明的bug,随着屏幕放置时间的变长,动画越来越慢,打开任务管理器,未见内存泄漏或者cpu使用率过高。打开performance,发现页面调帧严重,屏幕帧数越来越低,单个Frame CPU time越来越长,范围来看,script和render和paint耗时未发生线性变化,只有system时间越来越来长,越来越长,期望能被大佬告知原因
一开始

到后来

解决的办法较为…,光线每循环一个周期,我销毁了之前的canvas并新建了canvas,上层规避了system time不知道为什么越来越长的问题
chrome版本:80.0.3987.163(正式版本) (64 位)

版权声明:本文为zgddan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/zgddan/p/12715568.html