一个业务需求实现四个轮播组件的思路
需求原型假设有一列未知长度的数据,想在一个容器中展示轮播。基本结构如下我们需要实现的是组件实现思路1.我们使用css3的translateX进行平移滑动,在列表移出容器的瞬间,重置到容器右侧不可见的地方。第一步是完成布局,确定元器件的基本结构。
设置基本样式结构。旋转木马{位置:相对;显示:弹性;对齐项目:居中;&-wrap{溢出:隐藏;位置:相对;显示:弹性;弹性:1;对齐项目:居中;高度:100%;}&-content{transition-timing-function:linear;}}这样就完成了整体布局。您可以查看上面的视图了解详细信息。下一个问题是如何让元素移动。第二步设置动画,设置基本参数conststate=reactive({offset:0,duration:0})mainanimationimplementation//stylecontrolconstgetStyle=(data)=>;{返回{转换:data.offset?`translateX(${data.offset}px)`:'',transitionDuration:`${data.duration}s`,}}//轮播样式控制conststyle=computed(()=>getStyle(state))第三步是计算动画逻辑。首先需要计算具体的元素获取参数//containerwidthletwrapWidth=0//contentwidthletcontentWidth=0letstartTime=nullconstwrapRef=ref(null)constcontentRef=ref(null)//之后元素已安装,计算逻辑开始。onMounted(reset)通常暴露给外部选项constprops=defineProps({show:{type:Boolean,default:true,},speed:{type:[Number,String],default:30,},delay:{type:[Number,String],default:0,},})初始挂载计算逻辑constreset=()=>{//重置参数wrapWidth=0contentWidth=0state.offset=0state.duration=0clearTimeout(startTime)startTime=setTimeout(()=>{//拦截DOM未渲染阶段if(!wrapRef.value||!contentRef.value)returnconstwrapRefWidth=useRect(wrapRef).widthconstcontentRefWidth=useRect(contentRef).width//如果内容宽度超过容器宽度则运行if(contentRefWidth>wrapRefWidth){wrapWidth=wrapRefWidth;contentWidth=contentRefWidth;//calldoubleRaf(()=>{state.offset=-contentWidth/2;state.duration=-state.offset/+props.speed;})}},props.delay);}这段代码其实有几个要考虑的要点。doubleRaf函数的作用是什么?这是完整的功能代码。可以看到它只是简单的执行了回调exportfunctionraf(fn){returnrequestAnimationFrame(fn)}exportfunctiondoubleRaf(fn){raf(()=>raf(fn));}让我们回顾一下requestAnimationFrame做了什么window.requestAnimationFrame()告诉浏览器——你要执行一个Animation,并要求浏览器在下一次重绘更新动画之前调用指定的回调函数回调函数执行次数通常为每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数的执行次数通常与浏览器屏幕刷新次数匹配。为了提高性能和电池寿命,在大多数浏览器中,当requestAnimationFrame()在后台选项卡或隐藏的iframe中运行时,requestAnimationFrame()将被挂起以提高性能和电池寿命。一般情况下会根据浏览器提供最佳最快的执行时机,在隐身运行模式下会临时自动调用。这时候就会出现第二个问题:为什么要等到第二次重绘才执行回调呢?用法其实是我在研究vantUI库源码的时候看到的。他们的代码注释是这样写的//使用doubleraf确保动画可以启动PCemulator试过运行它,下次重绘时直接执行回调是可以的,但是实际运行时会有影响写入的因素。假设在最简单的模式下,我们希望一个元素的位移是translateX(0)->translateX(1000px)->translateX(500px)如果代码如下box.style.transform='translateX(1000px)'requestAnimationFrame(()=>{box.style.transform='translateX(500px)'})它的执行顺序会是,具体原因大家可以回忆一下requestAnimationFrame的作用translateX(0)->translateX(500px)既然你知道问题了现在,答案就出来了。第四步是重复上面图2所示的动画。当动画结束时,它被重置到容器右侧的一个不可见位置。首先,我们整个动画是用CSS3实现的,偏移值是直接设置的。然后通过过渡效果创建动画constgetStyle=(data)=>{return{transform:data.offset?`translateX(${data.offset}px)`:'',transitionDuration:`${data.duration}s`,}}现在我们知道CSS3过渡动画实现了,我们可以使用transitionend监听transitionend事件并在CSS完成转换后触发它。注意:如果转换在完成之前被删除,例如CSStransition-property属性被移除,过渡事件将不会被触发。constonTransitionEnd=()=>{state.offset=wrapWidthstate.duration=0raf(()=>{//使用双raf确保动画可以开始doubleRaf(()=>{state.offset=-contentWidth;state.duration=(contentWidth+wrapWidth)/+props.speed;});});}可以看到过滤结束后,会立即重置属性,然后更新状态。原理,它的主要关键步骤是在读取一个值时进行跟踪:代理的get处理函数中的track函数记录了属性和当前的副作用。检测值何时更改:调用代理上的设置处理程序。重新运行代码以读取原始值:触发器函数查找哪些副作用取决于该属性并执行它们。组件的模板被编译成渲染函数。渲染函数创建描述组件应该如何渲染的VNodes。它包含在一个副作用中,允许Vue在运行时跟踪被“触摸”的属性。渲染函数在概念上与计算属性非常相似。Vue不会准确跟踪依赖项是如何使用的,它只知道它们在函数运行的某个时刻被使用过。如果随后更改了这些属性中的任何一个,它将触发副作用再次运行,重新运行渲染函数以生成新的VNode。然后使用这些移动对DOM进行必要的修改。Vue在更新DOM时异步执行。当数据发生变化时,Vue会开启一个异步更新队列。视图需要等待队列中的所有数据变化完成后,再统一更新。所以理论上,考虑到性能损失,我们应该更新下一个队列中的动画。Vue提供了一个全局APInextTick,该API会将回调延迟到下一个DOM更新周期之后。更改一些数据后立即使用它以等待DOM更新。为什么不用nextTick再嵌套一层raf呢?vant源代码中有相关注释//等待Vue渲染偏移量//使用nextTick在iOS14中不起作用。整个代码实现思路已经完成,但是这种写法会有一个明显的缺点。整个动画只能融进融出。界面中间会有一段空白的元素等待动画进入场景,所以需要向下展开。考虑如何实现无缝轮播的效果。实现思路2我们直接复制两份相同的元素,然后使用延迟执行来达到无缝衔接的效果。第一步完成布局