先说我现在的方法吧
背景:项目是 react 环境,里面有个滚动数据的功能,没有引入 jquery,个人感觉单独为了一个animate
多引一个 jquery 不划算。
需求:展示获取到的 N 条数据,首屏展示 x 条,每过 t 秒滚动到下一屏。
实现:主要通过两个定时器,animate
控制滚动间隔。Interval
控制从滚动开始到滚动结束时长,代码如下。
目标:实现类似 jqueryanimate
的平滑效果,目前在内层的定时器设置 0.001 秒移动步长±1 像素,在小区域内还行,但是半屏或者全屏的滚动就显得速度慢了,修改增加步长又不平滑,有很明显的段落感。而 jqueryanimate
中就很流畅,所有动画都在 time 内完成。所以想请教下有没有更好的办法实现,或者有什么轻便的 animate 包。
代码:
……
//底部回滚到顶部
scroll = () => {
let dom = document.getElementById('scroll'),
//获取精确值
full = +window.getComputedStyle(dom).height.slice(0, -2),
view = +window.getComputedStyle(dom.parentNode).height.slice(0, -2),
//储存预设 top
top = 0,
//滚动值
scrollTop = 0,
//是否到底部
toTop = false;
function animate(){
top = toTop ? 0 : top - view
let Interval = setInterval(function(){
scrollTop = toTop ? scrollTop + 10 : scrollTop - 1
dom.style.marginTop = scrollTop + 'px'
if(toTop && scrollTop >= top){
toTop = false
dom.style.marginTop = top + 'px'
clearInterval(Interval)
}
if(!toTop && scrollTop <= top){
if(scrollTop - view <= -full) {
toTop = true
}
dom.style.marginTop = top + 'px'
clearInterval(Interval)
}
}, 1);
}
setInterval(animate, 4000)
}
//首尾循环
// scroll = () => {
// let dom = document.getElementById('scroll'),
// //获取精确值
// full = +window.getComputedStyle(dom).height.slice(0, -2),
// view = +window.getComputedStyle(dom.parentNode).height.slice(0, -2),
// li = +window.getComputedStyle(dom.firstChild).height.slice(0, -2),
// //滚动值
// scrollTop = 0,
// //储存 node
// liArr = []
// function animate(){
// //清空
// liArr = []
// //获取一屏的 node
// for (let i = 0; i < Math.ceil(view / li); i++){
// liArr.push(dom.childNodes[i])
// }
// //滚动事件
// let Interval = setInterval(function(){
// scrollTop -= 1
// dom.style.marginTop = scrollTop + 'px'
// //滚动到第二屏
// if(scrollTop <= -view){
// clearInterval(Interval)
// //重置
// scrollTop = 0
// //移动 node
// liArr.map(item => {
// dom.removeChild(item)
// dom.appendChild(item)
// })
// //复位
// dom.style.marginTop = '0px'
// }
// }, 1);
// }
// setInterval(animate, 4000)
// }
……
题外:顺便也可以指导下以上有哪些格式或者代码可以优化的
改用rAF实现动画,视觉效果可以了。还有点bug,切换标签或者最小化浏览器后rAF还在走,通过visibilityState判断是否执行/取消动画好像也没效果。虽然是用作展示的大屏页面不存在切换标签/最小化的情况,但还是想优化一下。因为有字数限制,只能贴部分主要代码。有什么好方法解决可以提供一下思路,也可以指导一下代码
……
let step = timestamp => {
//储存起始时间
if (!start) start = timestamp
//每帧的滚动位置
let progress = (timestamp - start) / 2;
dom.style.transform = 'translateY(' + -progress + 'px)'
//判断是否在前台显示,执行或暂停rAF并清空start
if(this.state.tabVisible){
stepID = window.requestAnimationFrame(step)
}else {
window.cancelAnimationFrame(stepID)
start = null
}
if (progress >= view) {
window.cancelAnimationFrame(stepID)
progress = 0
//初始化起始时间
start = null
dom.style.transform = 'translateY(' + -progress + 'px)'
}
}
if(+window.getComputedStyle(dom).height.slice(0, -2) > view && this.state.tabVisible){
interval = setInterval(() => {
mstep = window.requestAnimationFrame(step)
}, 4000)
}else {
clearInterval(interval)
window.cancelAnimationFrame(mstep)
}
……
上面提到的bug修复了,贴一下代码,字数限制删了注释。可以结合上面看一下
scroll = () => {
let dom = document.getElementById('scroll'),
fragment = document.createDocumentFragment(),
view = +window.getComputedStyle(dom.parentNode).height.slice(0, -2),
li = +window.getComputedStyle(dom.firstElementChild).height.slice(0, -2),
start = null, stepID = null, interval = null,
step = timestamp => {
let liArr = [], i = 0;
while (i < Math.ceil(view / li)) {
liArr.push(dom.children[i])
i++
}
if (!start) start = timestamp
let progress = (timestamp - start) / 2;
dom.style.transform = 'translateY(' + -progress + 'px)'
stepID = window.requestAnimationFrame(step)
if (progress >= view) {
window.cancelAnimationFrame(stepID)
progress = 0
start = null
dom.style.transform = 'translateY(' + -progress + 'px)'
liArr.map(item => {
fragment.appendChild(item)
return item
})
dom.appendChild(fragment)
}
}
if(+window.getComputedStyle(dom).height.slice(0, -2) > view){
interval = setInterval(() => {
window.requestAnimationFrame(step)
}, 4000)
}
document.addEventListener("visibilitychange", function() {
clearInterval(interval)
if(!document.hidden) {
interval = setInterval(() => {
window.requestAnimationFrame(step)
}, 4000)
}
});
}
接上 在修改的时候朋友提到可以用transition来做动画,后来就用transition试了一下,也一起贴上来吧。因为transition要分别写多个浏览器的监听,这里偷懒只按需求写了webkit,在其他浏览器可能会有问题。transition相比rAF应该更简单,不过因为没用过rAF写了一遍也有收获。至于计时用interval还是timeout还是看实际情况,这里感觉interval少调用一次会好一点。感谢一直指导的 @ChefIsAwesome
scroll = () => {
let dom = document.getElementById('scroll'),
//虚拟dom
fragment = document.createDocumentFragment(),
//获取精确值
view = +window.getComputedStyle(dom.parentNode).height.slice(0, -2),
li = +window.getComputedStyle(dom.firstElementChild).height.slice(0, -2),
interval = null,
fun = () => {
dom.style.transition = 'all 0.5s'
dom.style.transform = 'translateY(' + -view + 'px)'
}
if(+window.getComputedStyle(dom).height.slice(0, -2) > view){
interval = setInterval(fun, 4000)
}
dom.addEventListener('webkitTransitionEnd', function () {
dom.style.transition = 'all 0s'
let liArr = [], i = 0
while (i < Math.ceil(view / li)) {
liArr.push(dom.children[i])
i++
}
liArr.map(item => {
fragment.appendChild(item)
return item
})
dom.appendChild(fragment)
dom.style.transform = 'translateY(0px)'
}, false);
document.addEventListener("visibilitychange", function() {
clearInterval(interval)
if(!document.hidden) {
interval = setInterval(fun, 4000)
}
});
}
1
ChefIsAwesome 2019-04-17 16:28:20 +08:00 via Android 1
两个尝试的方向:
1 时间不对,应该用 raf 2 滚动的时候用 translate y |
2
kingsleydon 2019-04-17 16:39:59 +08:00
建议直接用库,滚动动画是很多动画库都有的基础功能,react-spring react-motion
|
3
leaveeel OP |
4
leaveeel OP @kingsleydon 谢谢,后面会尝试一下。一直都不习惯找库,看完需求就直接撸:XD。有的东西写出来挺爽的就是花时间
@ChefIsAwesome 尝试用了 rAF 基本都解决了,就是当页面在后台的时候再切回来会一直执行动画到当前时间,试了用 visibilityState 控制也没用,就是 `if(this.state.tabVisible)` 这段。是不是方法写的有问题,主要代码在附言里,有空的话能再帮我看看吗。 |
5
ChefIsAwesome 2019-04-19 23:40:57 +08:00
@leaveeel 现在的浏览器为了省电,定时器在后台是停止的,恢复的时候一下子就把累计的定时器跑完。
你可以: 1.尝试不用 setInterval,用 setTimeout,类似递归的方式来写。 loop = () => { if(shouldLoop) setTimeout(loop, delay) }; 2.尝试 debounce。这种短时间运行一堆东西,其实只有最后一个是有用的情况需要做 debounce。 |