本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
做可视化项目或者其他项目时,我们都可能会用到一些动画,当有些动画前端实现起来比较复杂时,UI组那边就会甩过来好几十张图片过来。在这些图片中,每一张图片代表一帧,我们可以利用类似走马灯的方式,把一张张图片按照一定顺序循环呈现到页面上实现动画效果。
为什么不用gif格式来播放动画呢?主要考虑的问题有两个:
1、UI组那边说gif格式会导致图像失真,边缘有锯齿;
2、使用序列帧图片的方式,通过代码方便控制,可以轻松控制其暂停或者播放;
1、通过css3中的animation属性
通过animation
属性不断变换元素的背景图来达到动画效果。
<div class="animation"></div>
//sass写法
$imgCount: 34;
.animation {
position: absolute;
width: 800px;
height: 800px;
animation: animation 2s infinite;
}
@keyframes animation {
@for $i from 1 through $imgCount {
#{($i * 100%/$imgCount)} {
background-image: url(./img/#{$i}.png);
}
}
}
执行上面的代码时会看到当动画第一次执行时,会一直闪烁。那是因为浏览器加载图片、渲染图片需要时间,有个空白过渡期,这时候可以利用浏览器缓存的策略,先让图片预加载。
let count = 0
const animationDiv = document.getElementsByClassName("animation")[0]
const imgCount = 34
const images = []
animationDiv.style.display = "none"
for (let i = 0; i <= imgCount; i += 1) {
images[i] = new Image()
images[i].src = `./img/${i}.png`
images[i].onload = () => {
count += 1
if (count === imgCount) {
animationDiv.style.display = "block"
}
}
}
上面的做法其实不太好,因为图片过多导致请求过多,可能会影响页面请求;
通常的做法是把全部的图片合成一张,通过改变background-postion
的值进行动画。
.animation {
position: absolute;
width: 800px;
height: 800px;
background-image: url(./img/img.png);
animation: animation 2s steps(34) infinite;
}
@keyframes animation {
0% {
background-position: 0;
}
100% {
background-position: 100%;
}
}
通过在animation元素上设置animation-play-state
为paused
或者running
可以控制动画停止或者播放;
2、通过canvas实现序列帧图片动画
<canvas width="800" height="800" id="canvas"></canvas>
<script>
let count = 0
let ctx = null
let timeout = null
const imgSrcs = []
const images = []
const imgCount = 34
const delay = 100
const canvas = document.getElementById('canvas')
for (let i = 0; i <= imgCount; i += 1) {
imgSrcs.push(`./img/${i}.png`)
}
const animation = (i) => {
ctx = canvas.getContext('2d')
if (!ctx) return
ctx.clearRect(0, 0, images[i].width, images[i].height)
ctx.drawImage(images[i], 0, 0, images[i].width, images[i].height)
if (i === imgSrcs.length - 1) {
timeout = setTimeout(() => {
animation(0)
}, delay)
} else {
timeout = setTimeout(() => {
animation(i + 1)
}, delay)
}
}
for (let i = 0; i < imgSrcs.length; i += 1) {
images[i] = new Image()
images[i].src = imgSrcs[i]
images[i].onload = () => {
count += 1
if (count === imgSrcs.length) {
animation(0)
}
}
}
</script>
当然,也可以将图片合成一张,通过drawImage
方法中的sx
,sy
,swidth
,sheight
四个参数不断的对图片进行裁剪实现动画,这里要注意的是使用canvas实现系列帧动画时,如果要把图片合成一张时,低版本的火狐和谷歌浏览器会空白显示不出来,也许是因为合成的图片像素太高了。
let ctx = null
let timeout = null
const delay = 100
const imgCount = 34
const canvas = document.getElementById('canvas')
const images = new Image()
images.src = "./img/img.png"
images.onload = () => {
animation(0)
}
const animation = (i) => {
ctx = canvas.getContext('2d')
if (!ctx) return
ctx.clearRect(0, 0, images.width, images.height)
ctx.drawImage(images, i * images.width, 0,images.width,images.height,0,0, images.width,images.height)
if (i === imgCount) {
timeout = setTimeout(() => {
animation(0)
}, delay)
} else {
timeout = setTimeout(() => {
animation(i + 1)
}, delay)
}
}
3、通过动态改变img标签路径实现系列帧图片动画
该方法也经常见,就是通过不断改变img标签中的路径,实现动画效果;
<img id="img">
<script>
const img = document.getElementById("img")
const delay = 100
const imgCount = 34
const animation = (i) => {
if (i === imgCount) {
animation(0)
return
}
img.src = `./img/1_000${i}.png`
img.onload = () => {
setTimeout(() => {
animation(i + 1)
}, delay);
}
}
animation(0)
</script>
以上是实现序列帧图片动画的3种方式,第一种使用animation
属性的方式应该是最常用、最简单的了,不需要写js。当然,当动画有其他需求时,可以采用第二种方式,通过canvas
可以变换出各种各样的动画出来。