Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ccforward opened this issue Mar 22, 2017 · 25 comments
Open

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

ccforward opened this issue Mar 22, 2017 · 25 comments

Comments

@ccforward
Copy link
Owner

ccforward commented Mar 22, 2017

渐进式图片加载 progressive-image

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

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

代码已经封装

先看简单例子 demo

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

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

CSS

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

GitHub、demo

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

GitHub 地址

NPM 地址

vue-demo

原生js-demo

图片裁剪

多数 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 库解决。

@shouhe
Copy link

shouhe commented Mar 22, 2017

get 以后懒加载尝试下的 体验棒棒的

@think2011
Copy link

啊,还想知道 canvas 是如何做的

@Yeahax
Copy link

Yeahax commented Mar 23, 2017

@shijunti19
Copy link

你不觉得眼睛被晃花了吗?大图片才希望有这种体验吧

@flxxyz
Copy link

flxxyz commented Mar 26, 2017

get 新技能

@think2011
Copy link

@Yeahax tks!

@yuyang2016
Copy link

老师您好,我是sdk.cn编辑于洋,请问可以将这篇文章转载到我们平台吗,请您放心我们会严格遵守转载规范,保护您的权益。

@ccforward
Copy link
Owner Author

@yuyang2016 可以

@yuyang2016
Copy link

谢谢!

@KoreSamuel
Copy link

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

发现一个错字,原图绝对定位,抱歉。

@Charles9292
Copy link

Charles9292 commented Apr 6, 2017

图片裁剪是指把原图通过插件处理了,等于需要两个链接吗.
不是说一张图片链接就能搞定.
我学习不好, 我不太理解

@ccforward
Copy link
Owner Author

@jsonformat
是需要两张图片的 一张原图 一张宽为20px等比缩小的缩略图

@zh30
Copy link

zh30 commented Jun 2, 2017

你好,我vue的项目用了您的组件,遇到这样的问题:首页大图用了这个效果,然后路由切换到其他页面后,再切换回首页,首页大图就不加载了,一直是模糊的初始状态。怎么样才能让它再次触发加载呢。

@ccforward
Copy link
Owner Author

@hakunamatatawang

不好意思 可能是个bug 我来复现下看看先

@zh30
Copy link

zh30 commented Jun 3, 2017

@ccforward
好的,辛苦。

@libin1991
Copy link

牛逼啊

@SikyChen
Copy link

@ccforward 您好,我也在用路由的时候,遇到了跟 @zhanghedate 一样的问题,切换路由后,再切换回来的时候,图片不加载一直处于模糊的状态,请问您这个问题解决了吗?

@ccforward
Copy link
Owner Author

@SikyChen

bug 已经修复 版本号 1.2.0

@ccforward
Copy link
Owner Author

@zhanghedate

bug 已经修复 版本号 1.2.0

@SikyChen
Copy link

@ccforward 是的,已经试过,非常棒!

@GodStream
Copy link

我的vue项目遇到这样一个问题,同一个页面我用到两个v-progressive指令,分别有两个板块的图片,但是最多只能加载出一个屏幕区域的图片,其它图片就会一直保持模糊状态不在加载,请问这该怎么解决呢?

@wangjuunwei
Copy link

请问如果是网络动态加载图片该如何处理呢

@meimei777
Copy link

image
你好我想请问一下,在图片的同一个位置我刷新一下浏览器,为什么会产生两个一样的img标签,我觉得应该始终都是一个才对。

@ccforward
Copy link
Owner Author

@meimei777 这个问题你提个 issue 我来看下吧

@leon-kfd
Copy link

是不是在IOS的Safari会失效,好像图片的onload事件在Safari会失效

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests