142人参与 • 2024-08-03 • 动画
最近开发的一个项目中,要求使用一个动画效果,ucd 提供了三种解决方案,各有优劣。
使用 ae插件 lottie-web
生成一个svg 动画,浏览器渲染svg,由于dom频繁的变动,即不断的重绘,这个动作会大量的占用cpu 和 gpu 的资源,针对一些比较老的机器(工厂、政府单位的机器可能比较老旧),可能会导致资源不够,打开浏览器cpu直接冲到100%导致浏览器卡死。
应用场景:针对特定性能比较强悍的机器,适用于全屏和局部界面的动画
视频或者gif 能很好的展示动画的完整性,资源占用也比较小,但是会有如下缺点:
不透明
, 无法和其他界面有效的整合展示应用场景:可以用于浏览器部分界面的动画
原理:人的视觉保留,1秒钟切换24张画面,人就感觉是一个动图
优点:
缺点:
使用场景:可以作为全屏的解决方案
由于ucd设计的动画是全屏的(要透明效果,放弃了gif和视频),但无法确保用户最终使用的设备是高性能的(放弃了svg方案),最终只能选择 多张图片快速切换实现的动画效果。
window.requestanimationframe()
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
在浏览器重绘的时候,修改目标控件的背景图片
<!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>document</title>
</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>
浏览器会一直出现白屏现象,一直到动画执行完成,呈现出最后状态的背景图片
由于动画是10秒钟要执行250张动画,即每张图片的间隔是40ms,因此可以尝试使用settimeout定时处理
<!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>document</title>
</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>
屏幕会不团的闪烁,出现“闪屏”现象
每隔40毫秒再次改变控件的背景,即改变的一瞬间,浏览器需要去从网上下载,图片还没有下载到本地,浏览器本地还没有图片渲染,界面就会白屏
考虑到 requestanimationframe 每次都是屏幕刷新的时候才会触发方法,即,图片变化一定是显示器刷新的时候,如果图片每次都能顺利呈现出来,即动画可以顺利完成,因此需要保证刷新的时候,图片是一定下载完成到本地了的。
由于是10秒钟250张图片,因此40ms才会跳转到下一章,这个时间作为网络下载图片和渲染图片的时间。
另外,为了节省网络下载的时间,因此决定先下载图片到本地缓存起来,40ms只用来做为渲染的时间,提高cpu的利用率。
用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>document</title>
</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">
<!-- <img src="./donghua/y_00000.png" /> -->
</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 + `<img src="./donghua/y_00${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>
使用5秒预先下载图片之后再执行动画脚本:动画执行比较流畅
不使用5秒下载图片,直接执行动画脚本:浏览器会出现闪屏的情况
不使用5秒下载图片,直接执行动画脚本:
由于两张图片之间的时间间隔是40ms,需要同时完成下载和展示,才能保证动画的连贯性。下载的时间要远大于cpu渲染图片的时间,如果下载的时间比较长,而背景已经变化,但是还没有下载下来,就会出现白屏,一旦下载了,就立马渲染出来,整个过程就会出现“白屏-渲染背景-白屏-渲染背景”的往复循环,即“闪动”现象。
使用5秒下载图片之后再执行动画脚本:
浏览器提前会下载图片并缓存到本地,执行动画的时候,下载过程就变成了从缓存获取,大大缩短了下载图片的时间,40ms的时间留有足够的时间用于渲染,看上去就比较流畅。
雪碧图:将多个图片合并为一张图片
动画原理:将雪碧图作为div 控件的背景,**然后定时器改变背景图的展示位置,在1秒钟内变换的次数超过24次,**人就会感觉是动画的效果
优点:
由于有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>document</title>
</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 + `<img src="./xuebi/${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>
在同一张雪碧图中切换背景,还是很流畅的,但是雪碧图切换的一瞬间,能很明显的感受到“动画”的抖动(图片切换很明显)
最好的解决办法:将所有的图片合并为一张雪碧图
设置一个div控件展示一张图片,将所有的图片全部放到该div中,隐藏滚动条,定时设定滚动条的位置(一张图片实际高度的整数倍),快速设置滚动条的位置,实现动画的连续性
<!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>document</title>
</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 + `<div class="imgcontainer"><img src="./donghua/y_00${numstr}.png"/></div>`
}
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>
动画很流畅,没有出现跳帧、抖动、白屏的情况
所有的图片全部都下载到本地了,并且也在内存中了,不存在下载和加载图片这两个过程,只有渲染,效率是最高的。
如果全屏动画放在最上层就会阻止用户点击到其他组件,最好希望这个动画像“透明”一样的存在,只是展示不监听任何事件,因此 css pointer-events
属性就是最好的选择
pointer-events: auto|none;
属性值:
auto | 默认值,设置该属性链接可以正常点击访问。 |
---|---|
none | 元素不能对鼠标事件做出反应 |
原理说明:在点击某个按钮之后,立马让按钮的属性设置为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)
}
})
}
})
原理说明:水印只是用于显示,并不希望被用户鼠标选中和参与点击事件,因此可以将其设置为“透明”
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论