轮播图踩坑指南 (工作日志)
好吧, 我终于变回了一个普通码农, 谢天谢地.
repo: https://github.com/fulvaz/Carousel-Exercises
具体效果看淘宝首页的轮播图
总算是写完了一个轮播图, 回想上次面试被人鄙视轮播图都写不好, 我明白了是为什么 —- 真的轮播图都写不好, 基础太差了.
因为一个轮播图就能涉及到许多问题了, 下面一个个来说明
设计, 降低耦合
如果不加任何设计直接写, 结果就是全局变量满天飞, 不同的view之间糅杂到一块, 要想办法将他们分开.
ps: 虽说是设计, 但是我依旧觉得还是可以继续改进.
到我这其实只是做了个监听者模式, 将页面逻辑和业务逻辑分离, 顺便说一下这里的页面逻辑是指与页面操作相关的逻辑, 比如说div位置, 动画, 形变等等; 业务逻辑是指, 用户点击了什么, 键盘做了什么操作
那接下来实现就很简单, 先实现一个监听者, 代码见.
https://github.com/fulvaz/Carousel-Exercises/blob/master/scripts/observer.js
来来回回就那么几个, 订阅, 取消订阅, 发射事件.
然后新建imgView, buttonView, 并且继承这个监听者, view只要监听事件, 然后处理用户点击时, emmit这个事件就可以了.
相比而言, 以前需要在处理click事件时还要处理页面逻辑, 不妥.
举个例子, 我刚开始希望只是点击左右导航, 然后图片相应切换, 没问题, 很容易.
那么我加这么个需求, 图片下面需要显示一排按钮, 点那个出哪个图片, 并且这排按钮还要通过高亮某个按钮, 指示现在是第几张图片.
这就日了狗了了啊, 刚才只是两个实体的单向联系:navigator -> imgView, 现在变成了三个实体navigator, imgView, button, 而且联系有navigator->imgView, navigator->button, button->imgView
最麻烦的是navigator->button, 你要在原理navigator中修改原来的逻辑, 原来不需要知道第几张, 现在需要了, 要重新处理循环滚动的问题, blablabla
如果要再加新功能…..联系更多的话……
所以还不如让他们只是负责自己的工作, 做完传个消息出去.
话说回来, 这个监听者模式其实是同步监听模式, 即如果在监听某个时间加入耗时操作, 直接页面被艹翻, 嗯, 异步? 说起来, 我深深感觉异步是个陨石坑…和多线程一样…
异步问题
我实现循环轮播, 有动画, 并且最后一张到第一张的动画无缝(实现不好就会出现最后一张绕了一大圈回到第一张的情况)
原理很简单, 比如需要播5张图, 将他们命名为1…5, 实现无缝循环的方法就是在1前面加一张最后一张, 在5后加入第一张, 顺序就会变成[5, 1, 2, 3, 4, 5, 1], 记这个数组为a.
当处于最后一张5(a[5]), 点击下一张, 那么就是到1(a[6]), 动画是5->1, 不这样, 动画效果是5->4->3->2->1
动画解决了, 动画后, 只需要将a[6]无动画切到a[1]就可以了(直接修改left值).
然后就踩到异步的坑里面去了.
我刚开始是利用css的transition属性做的, 那简单对吧
问题是, 修改style在浏览器是异步的啊, 而且会打包给你做
就是说, 你以为人家会乖乖给你按顺序做代码写的, 实际浏览器给你做的是
只有这两.
异步编程保证顺序的方法就是回调, css的动画的回调?
好吧, 我还是乖乖用requestAnimationFrame
关键点是注释那, 其他代码都是常见的用法.
动画只有一半, 图片只动了一半
因为按太快了, 有些糟糕的教程说是内存问题, 不能按太快 (题外话, 慕课网上的前端教程糟糠多于精华, DW, 糟糕的代码风格和瞎扯, 乖乖去MDN看, 或者买书, 学习总是枯燥的)
我本来想加一个处理click事件延时, 比如说100ms才能按一次按钮, 但是因为和下面一个问题, 我最后用了另一个方案:
根本解决方法是给imgView加一个busy属性, 当他在响应某个事件时设置这个标志位, 响应结束再取消
自动播放与点击
自动播放很简单, 用setTimeout就可以了(不推荐setInterval, setInterval会堆积任务 — 或者说, 产生奇怪的结果)
但问题来了, 如果自动播放同时快速点击下一张, 那么图片会变成空白
解决方法已经说了, 就是加busy属性, 但是setTimout后, 浏览器怎么处理任务?
首先, 我推测原因是点太快了, 首先我测试了将自动播放间隔变短, 确实直接空白, 是点太快了, 而且原因应该和循环播放有关, 从结果来看是没有重置到最开始的位置, 一直这么循环了下去
调试证明, 确实有时候没有运行动画后的回调.
推测: 是动画时间长, setTimout太短打断了动画, 所以我加快的动画速度, 确实没有问题了
猜测: 动画时间长, 前一个RAF重置了图片位置, 然后就被下一个RAF给改了, 嗯, 这个解释似乎最合情合理.
根据这个原因, 修改了一下代码, 不加busy判断也可以了.
原来的代码
修改后
主要不同是, 现在每帧动画只要在现在实时位置上添加上一段距离即可
之前是先记录调用动画时的距离, 每帧根据那个初始距离算出新的图片位置.
另外callback之前还设置了一下图片的位置, 因为这么改后, 图片位置老是对不上, RAF的过程是无法debug的, 但是debug时位置是对的, 我觉得很有可能是精度被忽略的原因? 还需要研究
moveHorizontal分析 (上述解决冲突方案无效)
moveHorizontal函数根据图片当前位置和接受到的offset, 然后移动图片.
当有多个RAF同时调用moveHorizontal时…..之前之所以没出大bug是因为offset都是一样的, 如果offset不同, 比如直接点击按钮, 刚好自动播放也要用, bug就来了
好吧, 上面说的解决方案其实没用.
需要保证同时只进行一个RAF, 就是说, 给imgView加锁 — busy属性