PhotoGallery技术改造
最近离职后除了准备面试,多了许多时间对以往的前端个人项目进行改造。PhotoGallery就是其中一个个人很喜欢,但由于技术原因没做到完美的例子。最近准备抽出时间进行完善。
PhotoGallery是一个使用瀑布流展示电影海报以及花絮(当然都是个人比较心水的)的展示型页面。所有的电影图片也是从大一就开始收集的,一直囤积在人人上。页面的诸多功能是根据个人爱好设计的,如
- 根据标签搜索
- 相似图片
- 基于tag的推荐等
总体来说,就是一个展示、介绍、推荐电影的地方。内容上还是很不错的。但是,去年寒假码代码时,前端技术还有待提高,很多地方写得并不严谨甚至比较丑陋。功能上也有些影响体验必须解决的痛点。大概有下面这些
- 首屏渲染时间糟糕,这是因为图片过多(个人看的太多),又使用了Vue。同时Vue这种MVVM框架和精细化DOM操作一山不容二虎,因此,结合懒加载,效果依然不理想
- 图片的时序排布并不自然,当时图省事,使用纯CSS方案实现瀑布流,牺牲了图片排序。图片只能从上到下再从左到右排序,和正常的阅读顺序并不一致。同时,最老的图片在最前,也不合理
- 新增图片困难,这是由于github.io的纯静态的限制,当时采用了静态图片+meta存数据的方式来实现,后面看了电影再往里加图步骤繁琐,没有人性化的办法
- 样式老气,细节粗糙
- 本地调试困难,只使用了gulp来压缩js,css和json代码(代码少,不需要打包),不是全家桶脚手架,本地调试困难,且不能使用ES6语法
- 代码语法和风格上不严谨,考虑结合在公司的规矩规范
针对上面大大小小几点,以及实际情况(比如只能使用github.io),考虑像下面这样优化
瀑布流布局实现方式待优化
放弃使用column-count的方案。原因有二:
- 排布顺序是从上到下,再从左到右,和日常经验相悖。类似地,使用flex的方案也不行
- 本身和懒加载的设计兼容性并不好,懒加载的新图片会导致整个页面的布局完全改变。类似地,使用grid的方案也不适合
因此考虑借鉴张鑫旭大神的方案,综合CSS和JS实现懒加载的滚动式瀑布流布局。
首先,根据屏幕宽度设置合理的列数,再逐列插入5张新图片,作为初始情况,同时,使用flex
布局,设置flex-grow
和justify-content
等属性。
1 | const Wall = { |
1 | #photos { |
之后,监听可能会改变布局的所有情况,在我这个场景下,大概有三种:
- 滚动(scroll)事件
- 缩放事件(resize)事件
- 筛选图片,在改变筛选条件,会导致图片数目的变化
下面分情况解决之。
resize时
监听window
的resize
事件,当最后一列的位置变化时,意味着布局已经改变,需要触发重排。可以看到上面的itemForColumns
中依赖columns
和lastFlag
两个状态。这里我们利用MVVM框架的优势,维护这两个值,就可以让Vue帮我们完成重排这样的繁琐操作。如下,当columns
改变时,才会触发重绘。
1 | window.addEventListener("resize", e => { |
筛选图片时
同理,通过关键词筛选图片时,改变了传入Wall的prop factor
。会同步更新依赖factor
的item
,触发重排。有一点有注意的是,**lastFlag
需要重新开始累加**。
1 | watch: { |
scroll时
页面滚动时,需要加入新的图片到column中,我们要做的只是更改lastFlag
即可,Vue会帮我们自动完成依赖lastFlag
的itemForColumns
更新。重点在,我们如何知道lastFlag应该增加到多少。
我们回看下itemForColumns
的逻辑,可以发现新增的图片是循环摆放的。这里额外说一句,尽管新图片放在最短列是最合理的,但是工程上并不合算(一是Vue下做这么精细的DOM操作不合适,二是获知最短列意味着DOM操作已经发生,即会有频繁的回流和重绘,这会影响渲染时间)。我们循环考虑每一列的最底部位置,如果在视口内,将图片更新到该列,直到所有列底部都在视口外。直到图片加载完毕。
另外,在实践时还发现一个问题,handleScroll
里更改了lastFlag
后,Vue本身有batch的优化,会在microtask栈空后,才会进行耗时的DOM操作。循环添加图片时,需要通过setTimeout异步完成,避免误判,在一次递归中加载了所有图片。
1 | handleScroll(top) { |
新增图片困难
由于github.io是纯静态的页面,我并没有后台环境,这个痛点短期内只能缓解不能根除。不过后面考虑将所有图片迁移到图床上,毕竟把图片数据也存在github上感觉还是……有点怪怪的。日后新增图片应该还是通过上传图片,更新meta.json
的形式完成。
目前已将所有图片迁移到图床上,图床选择上参考了知乎上的推荐,使用七牛云存储,在个人实名认证后,免费部分有每月10G国内和国外下载流量,100万次GET和PUT请求次数,和10G存储空间。同时,它还提供对图片的压缩等管理,尽量减少流量。
迁移之后,仓库体积大大减小。之后日常更新时,图片单独上传,根据外链固定前缀得到最终路径。
meta.json
的更新上,考虑自己写一个工具,根据新看的电影生成新的content。
已完成自动生成meta.json
的小工具,原理很简单,就不再介绍了。
细节美化
点比较细碎。整体借鉴了material design的思想。
影片详细信息的遮罩
考虑使用100%的遮罩,同时禁止背景滚动的形式展示图片的详细信息。起初打算用js去实现,后来发现下面的两点使得方案并不简单
- scroll事件不能被cancel,这意味着不能打断默认的滚动行为
- 从Mouse,Keyboard,Touch相关触发scroll事件的事件劫持滚动行为倒是可以,不过要监听的事件太多
只好作罢,通过纯CSS的方式,弹出浮层时,为body指定noscroll
的类名。让浮层的overflow
属性为scroll
即可,同时设置浮层position
属性为fixed
即可。
1 | #display { |
使用缓动函数改进回到开头
这里使用定义域和值域都是[0,1]
的easeInOutCubic
函数。
1 | const easeInOutCubic = t => (t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1); |
有了缓动函数后,使用requestAnimationFrame
即可高效率地绘制JS动画。这里封装了一个animate
函数。
1 | const animate = (obj, prop, end, time, ease) => { |
后面直接使用animate(document.body, "scrollTop", 0, 1000, easeInOutCubic)
就可以圆滑地上移了。
移动端的优化
- 使用媒体查询,在屏幕宽度更改时,隐藏一些元素
- 在UA为移动端设备时,给出提醒
loading样式
在改变筛选条件时,设置loading样式提升用户体验。通过积累onload的计数和初始加载图片值进行对比,在达到该值时清除遮罩。
1 | // ... |
杂项
- 导航条交互优化
- 导航条部分设置阴影,更改部分字体颜色和背景色
- 修改触发分类方式,由
click
改为mousemove
- 修改tab的样式
- 将vue和lodash的js文件下载到本地,避免CDN失效的问题(之前已经遇到过一次),增加可靠性
- 搜索条件不区分大小写
本地调试困难
因为代码较少,也只有一个文件,用不着webpack这样的全套解决方案。小巧易用的gulp就够了。针对我们需要的ES6转码,替换minify方案,本地调试等需要,都有对应的gulp插件解决问题。
gulp-babel
使用babel来转码,gulp-babel
依赖babel-core@6
或以上版本,同时设置preset
为es2015
或ES7相关版本时也需要下载对应module。
我只需要es2015即可。
1 | npm install --save-dev babel-core gulp-babel babel-preset-es2015 |
React和ES7的各阶段可以像下面这样选择安装
1 | $ npm install --save-dev babel-preset-react |
其他工具
- **gulp-uglify**,压缩代码
- gulp-rename,为压缩出的js重命名
- **gulp-webserver**,开启本地服务,方便本地调试
上面这些插件按照文档操作即可,坑比较少,使用webserver时的gulp.src()
入参通常为./
,指以当前目录为服务器根目录。
最后还需要加一个watch,方便在调试时的修改能同步转码压缩。像下面这样
1 | gulp.task('watch', function() { |
最后整个gulpfile.js
是下面的样子
1 | var gulp = require('gulp'), |
代码优化
从略。HTML和CSS部分参照以往写的建议即可。除此以外,优化了下面的部分:
- 删除了为兼容移动端额外使用的touchend事件,增加viewport的meta标签,消除移动端chrome浏览器点击300ms延时情况
- 由于引入了babel,删除了兼容ES6语法的自己写的polyfill部分
- 使用fetch API请求json
- 优化导航条点击事件处理相关的代码
- 减少图片数目和json体积