微信号:programmer_club

介绍:程序员第一自媒体,与你探讨码农人生路上遇到的各类泛技术话题,定期为你推荐码农人生思考、感悟以及启迪!

适用于 vue.js 和原生 js 的渐进式图片加载

2017-04-30 21:59 ccforward

作者:ccforward     

原文:github.com/ccforward/cc/issues/64

点击文末阅读原文即可前往) 


知乎和 Medium 都用了 progressive image (渐进式图片加载),用低分辨率的模糊图片来做预览图,代替以前懒加载图片时用的 logo 占位图。预览图大小也在平均 2KB~3KB 之间,虽然 cdn 流量上有所增加,但用户体验却非常好。

知乎和 Medium 使用的是动态绘制 canvas 这种比较复杂的方式来展现模糊效果,所以来实现一个只需要 HTML、CSS、JS 就能实现的渐进式图片加载。


代码已经封装

GitHub 地址:https://github.com/ccforward/progressive-image

NPM 地址:https://www.npmjs.com/package/progressive-image

先看简单例子 demo:https://ccforward.github.io/progressive-image/example/index.html


HTML


基本的 HTML 结构如下

<div class="progressive">  <img class="preview lazy" data-src="origin.jpg" src="preview.jpg" /> </div>

origin.jpg 就是原图,preview.jpg 是等比压缩后的预览图,比如原图是 400x200 则预览图可以使 20x10。


如果原图是 400x100 按照 4:1 的比例,预览图如果宽度是30,那么高度就是 7.5,所以图片裁剪这里会有坑,后面会遇到。


CSS


容器的基本样式

.progressive {  position: relative;  display: block;  overflow: hidden; }

外层的 .progressive 默认是 div 也可以是其他任何需要的包裹元素


图片容器可以是固定尺寸,也可以是固定的宽高比(用 padding-top 的方式来确保容器的固定的宽高比例),这就保证了原始图片加载之后不会出现容器尺寸的变化,然而这就必须计算每张图片的宽高比例。


知乎和 Meduim 都是获取到了原图尺寸,然后保持预览图(canvas)也是相同的尺寸,这样即使预览图被裁剪后和原图尺寸的比例不一致,也不会出现容器尺寸在原图加载之后会抖动的bug。


我们退而求其次,采取更简单粗暴的方式来做:

  1. 预览图和原始图尽量保持相同的宽高比

  2. 原图加载完后,不删除预览图,而是设置为 opacity:0


PS: 如果预览图和原图的比例不一致,同时原图加载后删除预览图,就会出现抖动,如下图

原图尺寸 800x533 裁剪后如果按照比例应该是 20x13.325 但实际是 20x13 ,所以加载原图后空间不够,往下伸展出一部分。因此为了防止抖动的简单处理就加上第二点:不删除原图并设置为透明。


容器内图片


预览图和原图都充满容器

.progressive img {  display: block;  width: 100%;  max-width: 100%;  height: auto;  border: 0 none; }

预览图模糊处理

  • blur(2vw) 是为了保持相同的模糊度,而页面的尺寸无关。

  • transform: scale(1.05); 添加图片过渡动画

.progressive img.preview {    filter: blur(2vw);    transform: scale(1.05);  }

原图局对定位于容器左上角,是为了在动画阶段能覆盖原图。

.progressive img.origin {  position: absolute;  left: 0;  top: 0;  animation: origin 1s ease-out; } @keyframes origin {  0% {    transform: scale(1.1);    opacity: 0;  }  100% {    transform: scale(1);    opacity: 1;  } }


JavaScript


js的处理就简单多了,和懒加载图片没什么区别,主要一个 checkImage 函数在 DOMReady 和 scroll 时检测可视区 view 内是否有图片需要懒加载


以及 loadImage 函数用来替换预览图,加载原图触发动画。

function checkImage() {  const lazys = document.querySelectorAll('img.lazy')  const l = lazys.length  if(l>0){    for (var i = 0; i < l; i++) {      var rect = lazys[i].getBoundingClientRect()      if (rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0) {        loadImage(lazys[i])      }    }  }else {    events(window, false)  } }



响应式图片


demo 里最后一张图片用了响应式图片


data-srcset="./progressive/4.jpg 960w, ./progressive/4-m.jpg 1280w, ./progressive/4-l.jpg 1920w"

<div class="progressive full">    <img class="preview lazy" data-src="./progressive/4.jpg"  src="./progressive/r4.jpg" />  </div>

浏览器如果支持 img 标签的 srcset 将会根据 viewport 的宽度选取适当的图片来加载。


封装代码


JS

代码封装为 原生js 和 Vue.js 两种


原生js为一个 Class ,实例化后直接使用,封装后 140 左右的代码

index.js(https://github.com/ccforward/progressive-image/blob/master/src/index.js


Vue.js 版本为一个 v-progress 的指令,只要在需要的 img 标签上添加即可,总共 190 行左右的代码

index-vue,js(https://github.com/ccforward/progressive-image/blob/master/src/index-vue.js


CSS

抽取用于动画的 css 样式,写成 stylus 总共才 36 行

index.styl(https://github.com/ccforward/progressive-image/blob/master/src/index.styl


GitHub、demo

仓库的 README.md 文件有详细的使用说明


GitHub 地址(https://github.com/ccforward/progressive-image

NPM 地址(https://www.npmjs.com/package/progressive-image

vue-demo(https://ccforward.github.io/progressive-image/example/demo-vue.html

原生js-demo(https://ccforward.github.io/progressive-image/index.html



图片裁剪


多数 cdn 都会提供图片上传后裁剪的功能


node.js 有一个很好用的图片裁剪模块 gm


裁剪代码也很简单:

const gm = require('gm') gm('./coding.jpg') .resize(20) .write('r.jpg', function (err) {  err && console.log(err) })

对于简单裁剪 PHP 直接用 gd 库, python 可以用 pillow 库解决。




微信公众号内回复数字“1”

小编拉你进粉丝微信群

不是在文章评论里回复

 
程序员之家 更多文章 大型网站技术架构 如果只有一个月的时间学 iOS 开发,我们该做些什么? 金庸笔下的良好代码风格 代码真的有必要写到完美吗? 为什么程序猿会选择跳槽?
猜您喜欢 可以免费自学编程的12个网站 (70) 原子变量和CAS \/ 计算机程序的思维逻辑 深入解读《Gartner2017年商业智能和分析平台魔力象限报告》 互联网大佬都曾是程序员,我到底差在哪儿? 大长腿的诱惑,晚上约么?