react图片查看器实现
遥想在Flipboard实习时,我的第一个react项目里曾经有一个小需求就是为一个图集页面实现一个图片查看器。当时能力所限只实现了一版非常丑陋的勉强能用的,sad~如今再回头发现自己已经有能力实现一个体验更好的图片查看器,那么就弥补一下遗憾喽。
Demo
先放一下最后的成果。
功能
- 类似原生的滑动切换
- 双击放大和还原
- 图片拖动
实现
整个项目是用react16实现的,手势控制使用了alloyfinger,相较于另一个非常知名的web手势库hammer.js,alloyfinger更小,虽然功能可配置能力不如hammer.js,但是也足够用了。开发环境使用了storybook,storybook有比较丰富的插件,不过我这里没用(唯一个有迫切需求的移动端模拟插件,还是在下个版本才放出。。。)测试使用jest + enzyme。
实现过程中,有几个需要考虑的要点在这里记录一下。
切换
其实滑动切页是很常见的需求,jQuery的插件也数不胜数,但是这里面有个问题。很多插件会把所有图片都预先渲染好,如果图片数量较大,对于性能还是有一定影响的。理想状态下,应该只需要渲染最多三张,包括当前页,上一页,下一页。每次切换后再更新新的三页。
但是这样会带来新的问题,首先react在进行列表渲染时需要知道每一项的key,来决定如何优化,组件key发生变化会重新渲染整个组件,很多情况下没有必要,此外为了支持渲染两张相同url的图片,只使用url作为key也不合适。所以最后使用url+图片数组index来作为id。
1 | const displayMax = index + 2 > images.length ? images.length : index + 2; //获取下一页的index |
其中images为url数组,index为当前页数。
另外一个问题是如果我们渲染所有图片,偏移量计算很简单index * window.innerWidth
,翻到下一页,index 加一即可,但是如果只渲染3张的话,偏移量始终为1 * window.innerWidth
,翻到下一页偏移量变化过程为1 * window.innerWidth -> 1 * window.innerWidth + 手指划过距离(直到触发index + 1) -> 1 * window.innerWidth
这样的话过渡动画会很奇怪,左右闪。所以我这里的实现为1 * window.innerWidth -> 1 * window.innerWidth + 手指划过距离(直到触发index + 1) -> 0 * window.innerWidth + 手指划过距离 -> 1 * window.innerWidth
这里会在chrome上又又又碰到一坑。。。chrome在计算transform偏移时,如1 * window.innerWidth + 手指划过距离(直到触发index + 1) -> 0 * window.innerWidth + 手指划过距离 -> 1 * window.innerWidth
会做优化将变化合并,即把0 * window.innerWidth + 手指划过距离
这一步忽略了,所以我在这一步通过手动修改其他属性,来强制触发。翻到上一页同理。
1 | gesturesManager.on('touchEnd', e => { |
(后来想到另外一种做法是监听transitionend事件,再触发翻页)
拖动
其实这里和滑动翻页纠缠在一起,核心就是如何确定触摸事件触发图片拖动还是滑动翻页。在FLipboard时这个问题通过滚动条交给浏览器来解决,但是滚动条在pc上很丑陋,所以这次决定还是自己控制。控制优先级为:当图片拖动到边界时,控制权交给滑动翻页。用户双击放大控制权交还图片。超出边界判定的规则是图片偏移量大于图片宽度减去视口宽度的1/2.
1 | if ( |
优化和兼容
- 对于图片元素设置will-change提升为合成层使用GPU加速。
- 针对uc和webview中左滑后退,再touch事件中调用preventDefault。
- 移动端需要解决滚动穿透的问题的(最简单的方案body高度设为0)
未解决的问题
- pinch手势放大缩小的功能没有实现,核心问题在于如何计算放大中心。
- 组件进入的时候缺少过渡效果。