fabric.js动画制作简单的落雪效果播放
老规矩先上代码
1 <template> 2 <div class="animate"> 3 <el-button 4 id="toggle" 5 @keyup.space.native 6 class="elBtn" 7 :class="{ active: isFirst && autoPlay }" 8 >▷</el-button 9 > 10 <canvas id="canvas"></canvas> 11 </div> 12 </template> 13 14 <script> 15 import { fabric } from "fabric"; 16 export default { 17 data() { 18 return { 19 // radius/left/top/opacity is belong to circle 20 radius: [], 21 left: [], 22 top: [], 23 opacity: [], 24 // decide the tag of first pause 25 isFirst: false, 26 // autoPlay is the same statement with [playing] 27 autoPlay: false, 28 }; 29 }, 30 mounted() { 31 let playing = false; 32 let snowBallN = 100; 33 let cir = []; 34 35 this.radius = Array(snowBallN).fill(""); 36 this.left = Array(snowBallN).fill(""); 37 this.top = Array(snowBallN).fill(""); 38 this.opacity = Array(snowBallN).fill(""); 39 40 const windowSize = { 41 width: "1000", 42 height: "600", 43 }; 44 45 // 产生静态 canvas 46 const canvas = new fabric.StaticCanvas("canvas", { 47 height: windowSize.height, 48 width: windowSize.width, 49 }); 50 51 //animate(设置动画属性,动画结束值,可选对象) 52 //可选对象为动画详细信息,包括持续时间、回调、缓动等 53 //在每个动画帧上调用canvas.renderAll才能够看到实际的动画,重新渲染 54 55 // add snowBallN circle 56 for (let i = 0; i < snowBallN; i++) { 57 // cir Array is the gather of the single CIRCLE ANIMATION 58 cir[i] = new fabric.Circle({ 59 radius: getRandomInt(0.1, 1), 60 left: getRandomInt(0, windowSize.width), 61 top: getRandomInt(0, windowSize.height), 62 opacity: getRandomInt(0.1, 1), 63 fill: "#fff", 64 }); 65 // tag the last add cir[i] 66 cir[i].lastAdd = i === snowBallN - 1; 67 canvas.add(cir[i]); 68 // set animate when it achieve the last circle 69 playing && setAnimate(cir[i]); 70 } 71 console.log("Circle Array", cir); 72 73 // pause button 74 document.querySelector("#toggle").addEventListener("click", (e) => { 75 const targetEl = e.target; 76 this.isFirst = true; 77 78 // When playing is true, it should save the statement. 79 if (playing) { 80 targetEl.innerHTML = "▷"; 81 82 for (let i = 0; i < snowBallN; i++) { 83 this.radius[i] = canvas.getObjects()[i].radius; 84 this.left[i] = canvas.getObjects()[i].left; 85 this.top[i] = canvas.getObjects()[i].top; 86 this.opacity[i] = canvas.getObjects()[i].opacity; 87 } 88 // EveryTime, this array and the next is same, 89 // but the root of CANNOT SAVE is the simple CIRCLE. 90 // Just load every circle into the big cir Array, it's done. 91 // console.table("暂停Before Pause this.left", this.left.slice(0, 5)); 92 // console.table("Before Pause this.top", this.top.slice(0, 5)); 93 } else { 94 // Else, it should load in the shape what'll move. 95 targetEl.innerHTML = "▢"; 96 // console.log("this.left[0]", this.left[0]); 97 // think about the initial suitation, it should be divided. 98 // When radius/left/top/opacity array isn't null, it should reload in cir. 99 if (this.left[0]) { 100 for (let i = 0; i < snowBallN; i++) { 101 cir[i].set("radius", this.radius[i]); 102 // console.log("INNER DPACE", this.left[i]); 103 cir[i].set("left", this.left[i]); 104 cir[i].set("top", this.top[i]); 105 cir[i].set("opacity", this.opacity[i]); 106 } 107 } 108 // console.table("开始Before Play this.left", this.left.slice(0, 5)); 109 // console.table("Before Play this.top", this.top.slice(0, 5)); 110 // When it begin to play first, every circle need to set Animation. 111 canvas.getObjects().forEach((key) => setAnimate(key)); 112 } 113 this.autoPlay = !this.autoPlay; 114 115 // Change statement model 116 playing = !playing; 117 }); 118 // get Random number 119 function getRandomInt(min, max) { 120 return Math.floor(Math.random() * (max - min + 1)) + min; 121 } 122 123 // 设定动画函数 124 function setAnimate(circle) { 125 // 变化半径 126 circle.animate("radius", getRandomInt(0.1, 5), { 127 duration: getRandomInt(1000, 5000), 128 }); 129 // 变化透明度 130 circle.animate("opacity", getRandomInt(0.1, 0.9), { 131 duration: getRandomInt(1000, 5000), 132 }); 133 // 变化坐标 134 circle.animate("left", getRandomInt(0, windowSize.width), { 135 easing: fabric.util.ease.easeInOutCubic, 136 duration: getRandomInt(1000, 5000), 137 }); 138 // 变化坐标 139 circle.animate("top", getRandomInt(0, windowSize.height), { 140 // onChange and onComplete is two statement about the animation 141 // onChange decide the movement, 142 // onComplete decide the end action when the animation ends. 143 onChange: () => { 144 if (circle.lastAdd) playing && canvas.renderAll(); 145 }, 146 onComplete: () => playing && setAnimate(circle), 147 easing: fabric.util.ease.easeInOutCubic, 148 duration: getRandomInt(1000, 5000), 149 }); 150 } 151 }, 152 }; 153 </script> 154 <style> 155 .animate { 156 background-image: url(../assets/black.png); 157 position: absolute; 158 display: flex; 159 justify-content: center; 160 align-items: center; 161 } 162 #toggle { 163 position: absolute; 164 z-index: 999; 165 width: 200px; 166 height: 200px; 167 font-size: 100px; 168 } 169 .elBtn { 170 opacity: 0.4; 171 } 172 .active { 173 opacity: 0; 174 } 175 </style>
这个落雪效果是我仿照
这个网页的一百个圆随机动画制作的,加入了暂停与恢复播放的状态保存,优化了暂停播放按钮效果,在第一次通过鼠标点击播放后能够使用空格控制播放。
【注意点:每次animation动画暂停(onChange),如果不保存状态,就会从随机位置再次播放!←我设定的是这样的】
做的还是有点麻瓜,过程也有点不撞南墙不回头的意思,一直重复着一百个雪花的失败,脑袋都不清醒了,晚上睡觉前忽然想到要控制下数量,化繁为简,早上立马就发现问题所在了!总之,还是要淡定~
效果如图
人生到处知何似,应似飞鸿踏雪泥。