你好我想请问一下,在图片的同一个位置我刷新一下浏览器,为什么会产生两个一样的img标签,我觉得应该始终都是一个才对。
59.适用于 vue.js 和原生 js 的渐进式图片加载
渐进式图片加载 progressive-image
知乎和 Medium 都用了 progressive image (渐进式图片加载),用低分辨率的模糊图片来做预览图,代替以前懒加载图片时用的 logo 占位图。预览图大小也在平均 2KB~3KB 之间,虽然 cdn 流量上有所增加,但用户体验却非常好。
知乎和 Medium 使用的是动态绘制 canvas 这种比较复杂的方式来展现模糊效果,所以来实现一个只需要 HTML、CSS、JS 就能实现的渐进式图片加载。
代码已经封装
先看简单例子 demo
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
容器的基本样式
css
.progressive {
position: relative;
display: block;
overflow: hidden;
}
外层的 .progressive 默认是 div
也可以是其他任何需要的包裹元素
图片容器可以是固定尺寸,也可以是固定的宽高比(用 padding-top
的方式来确保容器的固定的宽高比例),这就保证了原始图片加载之后不会出现容器尺寸的变化,然而这就必须计算每张图片的宽高比例。
知乎和 Meduim 都是获取到了原图尺寸,然后保持预览图(canvas)也是相同的尺寸,这样即使预览图被裁剪后和原图尺寸的比例不一致,也不会出现容器尺寸在原图加载之后会抖动的bug。
我们退而求其次,采取更简单粗暴的方式来做:
- 预览图和原始图尽量保持相同的宽高比
- 原图加载完后,不删除预览图,而是设置为
opacity:0
PS: 如果预览图和原图的比例不一致,同时原图加载后删除预览图,就会出现抖动,如下图
原图尺寸 800x533 裁剪后如果按照比例应该是 20x13.325 但实际是 20x13 ,所以加载原图后空间不够,往下伸展出一部分。因此为了防止抖动的简单处理就加上第二点:不删除原图并设置为透明。
容器内图片
预览图和原图都充满容器
css
.progressive img {
display: block;
width: 100%;
max-width: 100%;
height: auto;
border: 0 none;
}
预览图模糊处理
blur(2vw)
是为了保持相同的模糊度,而页面的尺寸无关。transform: scale(1.05);
添加图片过渡动画
css
.progressive img.preview {
filter: blur(2vw);
transform: scale(1.05);
}
原图绝对定位于容器左上角,是为了在动画阶段能覆盖原图。
css
.progressive img.origin {
position: absolute;
left: 0;
top: 0;
animation: origin 1s ease-out;
}
origin {
0% {
transform: scale(1.1);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
JavaScript
js的处理就简单多了,和懒加载图片没什么区别,主要一个 checkImage
函数在 DOMReady 和 scroll 时检测可视区 view 内是否有图片需要懒加载
以及 loadImage
函数用来替换预览图,加载原图触发动画。
js
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"
html
<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
Vue.js 版本为一个 v-progress
的指令,只要在需要的 img
标签上添加即可,总共 190 行左右的代码
index-vue,js
CSS
抽取用于动画的 css 样式,写成 stylus 总共才 36 行
index.styl
GitHub、demo
仓库的 README.md 文件有详细的使用说明
图片裁剪
多数 cdn 都会提供图片上传后裁剪的功能
node.js 有一个很好用的图片裁剪模块 gm
裁剪代码也很简单:
js
const gm = require('gm')
gm('./coding.jpg')
.resize(20)
.write('r.jpg', function (err) {
err && console.log(err)
})
对于简单裁剪 PHP 直接用 gd 库, python 可以用 pillow 库解决。
该提问来源于开源项目:ccforward/cc
- 点赞
- 写回答
- 关注问题
- 收藏
- 复制链接分享
- 邀请回答
25条回答
为你推荐
- vue.js 初学者关于把一段template写进js的问题
- vue.config.js文件中使用vue实例挂载的变量
- vue.js
- 2个回答
- vue 导入外部js 无法执行外部js代码
- vue导入外部JS,为什么不能执行外部js的代码?
- 怎么在Vue框架里使用原生的javascript代码,我真的不知道Vue怎么用,太难了
- javascript
- vue.js
- 6个回答
- vue图片加载问题,鼠标移入显示图片,移除不显示,但是加载后都显示未加载
- vue cli4 全局引入js
- javascript
- vue.js
- 1个回答
- uni-app的示例项目中,App.vue是在哪里被挂载的?
- javascript
- vue.js
- 1个回答
- vue 按需加载 require.ensure 感觉有点慢,怎么解决啊?
- VUE vue.config.js 配置 代理不生效
- 刚开始接触vuex,login.vue文件中this.$store.state.token,总是报错——无法解析$store
- 使用wfs.js时不能调用其内部Websocket关闭和初始化函数,如何在外部调用?
- javascript
- vue.js
- 2个回答
- 将PHP变量传递给vue.js vue组件
- 关于vue.js中data里面数组和对象的理解和显示问题
- javascript
- vue.js
- 1个回答
- vue cli3中跨域及vue.config.js配置问题
- VUE.JS 中把computed改成methods后就失效了,为什么?
- vue输入域名自动加载index.vue?
- vue.js
- 3个回答
- Vue.js 使用element-ui 做走马灯,图片一直显示不出来
- Vue.js 关于Vue.js中样式引入问题
- vue.js
- 1个回答
- vue.js和jquery的validate怎么结合使用