专栏首页木子墨的前端日常懒就是生产力之图片懒加载

懒就是生产力之图片懒加载


最近负责的项目渐渐的由业务型转向营销型,营销内容越来越多,图片也就多了起来。图片一多起来问题就来了,一上来几十张图片加载起来半天都过去了,咋办?凉拌--懒加载

什么是懒加载

懒加载也叫延迟加载,本质上就是按需加载,即只有当图片dom已经在或者即将进入用户视线范围内的时候才去加载对应的dom图片。前两年流行的瀑布流其实就是按需加载的一个应用实例。

在懒加载的模型中,一个页面可以氛围三个区域如图,视图区、即将进入视图区、视图远区(即图中的不加载区)。

当一个图片在视图区或者随着屏幕滚动即将进入视图区的时候,就开始加载图片。

懒加载的处理流程

大象装冰箱,也得分三步,懒加载也一样 首先,将图片标签加入懒加载队列lazyQueue,并且给图片加载一个默认图,一般是个品牌logo或者1px*1px的空白图片做拉伸。加载默认图主要是为了看起来不那么尴尬,用户普遍尴尬耐受力强的可以不加~ 其次,添加屏幕滚动监听事件,如mousemove、wheel滚动等,实时监听懒加载队列lazyQueue中的dom位置变动。这一步算是懒加载的核心了吧。 最后,在监听器中实时判断图片dom的位置,如果已进入加载区的位置就去加载图片,加载完成后把响应的dom移出lazyQueue。 直接上代码吧,怎么感觉这么墨迹。。。

完整代码

/* global Image */
if (!Array.prototype.$remove) {
    Array.prototype.$remove = function(item) {
        if (!this.length) return
        const index = this.indexOf(item)
        if (index > -1) {
            return this.splice(index, 1)
        }
    }
}

export default (Vue, Options = {}) => {
    const isVueNext = Vue.version.split('.')[0] === '2'

    const DEFAULT_URL = Options.bgImgUrl || 'https//img.aiyoumi.com/null/2018423/101212916/20180423101212_1x1.png?height=1&width=1'

    const Init = {
        preLoad: Options.preLoad || 1.8,
        error: Options.error || DEFAULT_URL,
        loading: Options.loading || DEFAULT_URL,
        attempt: Options.attempt || 3,
        scale: Options.scale || window.devicePixelRatio,
        hasbind: false
    }

    const Listeners = []
    const imageCache = []

    const throttle = function(action, delay) {
        let timeout = null
        let lastRun = 0
        return function() {
            if (timeout) {
                return
            }
            let elapsed = (+new Date()) - lastRun
            let context = this
            let args = arguments
            let runCallback = function() {
                lastRun = +new Date()
                timeout = false
                action.apply(context, args)
            }
            if (elapsed >= delay) {
                runCallback()
            } else {
                timeout = setTimeout(runCallback, delay)
            }
        }
    }

    const _ = {
        on(el, type, func) {
            el.addEventListener(type, func)
        },
        off(el, type, func) {
            el.removeEventListener(type, func)
        }
    }

    const lazyLoadHandler = throttle(() => {
        for (let i = 0, len = Listeners.length; i < len; ++i) {
            checkCanShow(Listeners[i])
        }
    }, 300)

    const onListen = (el, start) => {
        if (start) {
            _.on(el, 'scroll', lazyLoadHandler)
            _.on(el, 'wheel', lazyLoadHandler)
            _.on(el, 'mousewheel', lazyLoadHandler)
            _.on(el, 'resize', lazyLoadHandler)
            _.on(el, 'animationend', lazyLoadHandler)
            _.on(el, 'transitionend', lazyLoadHandler)
        } else {
            Init.hasbind = false
            _.off(el, 'scroll', lazyLoadHandler)
            _.off(el, 'wheel', lazyLoadHandler)
            _.off(el, 'mousewheel', lazyLoadHandler)
            _.off(el, 'resize', lazyLoadHandler)
            _.off(el, 'animationend', lazyLoadHandler)
            _.off(el, 'transitionend', lazyLoadHandler)
        }
    }

    const checkCanShow = (listener) => {
        if (imageCache.indexOf(listener.src) > -1) {
            return setElRender(listener.el, listener.bindType, listener.src, 'loaded')
        }
        let rect = listener.el.getBoundingClientRect()
        if ((rect.top < window.innerHeight * Init.preLoad && rect.bottom >= 0) && (rect.left < window.innerWidth * Init.preLoad && rect.right >= 0)) {
            render(listener)
        }
    }

    const setElRender = (el, bindType, src, state) => {
        // 避免重复render
        let stateDone = el.getAttribute('lazy') === 'loaded'
        if (stateDone) {
            return
        }

        if (!bindType) {
            el.setAttribute('src', src)
        } else {
            el.setAttribute('style', bindType + ': url(' + src + ')')
        }

        // 默认会给图片添加有渐显效果的类名
        if (state === 'loaded' && (el.className.indexOf('animation__fade') === -1)) {
            el.className += ' animation__fade'
        }
        el.setAttribute('lazy', state)
    }

    const render = (item) => {
        if (item.attempt >= Init.attempt) {
            return false
        }
        item.attempt += 1
        loadImageAsync(item)
            .then((image) => {
                setElRender(item.el, item.bindType, item.src, 'loaded')
                imageCache.push(item.src)
                Listeners.$remove(item)
            })
            .catch((error) => {
                setElRender(item.el, item.bindType, item.error, 'error')
            })
    }

    const loadImageAsync = (item) => {
        return new Promise((resolve, reject) => {
            let image = new Image()
            image.src = item.src

            image.onload = function() {
                resolve({
                    naturalHeight: image.naturalHeight,
                    naturalWidth: image.naturalWidth,
                    src: item.src
                })
            }

            image.onerror = function() {
                reject()
            }
        })
    }

    const componentWillUnmount = (el, binding, vnode, OldVnode) => {
        if (!el) {
            return
        }

        for (let i = 0, len = Listeners.length; i < len; i++) {
            if (Listeners[i] && Listeners[i].el === el) {
                Listeners.splice(i, 1)
            }
        }

        if (Init.hasbind && Listeners.length === 0) {
            onListen(window, false)
        }
    }

    const checkElExist = (el) => {
        let hasIt = false

        Listeners.forEach((item) => {
            if (item.el === el) hasIt = true
        })

        if (hasIt) {
            return Vue.nextTick(() => {
                lazyLoadHandler()
            })
        }
        return hasIt
    }

    const addListener = (el, binding, vnode) => {
        /* if (el.getAttribute('lazy') === 'loaded') {
            return
        }
        if (checkElExist(el)) {
            return
        } */
        // 跳过不必要刷新
        if (binding.value === binding.oldValue) {
           return 
        }

        let parentEl = null
        let imageSrc = binding.value
        let imageLoading = Init.loading
        let imageError = Init.error

        if (typeof binding.value !== 'string') {
            imageSrc = binding.value.src
            imageLoading = binding.value.loading || Init.loading
            imageError = binding.value.error || Init.error
        }
        if (binding.modifiers) {
            parentEl = window.document.getElementById(Object.keys(binding.modifiers)[0])
        }

        setElRender(el, binding.arg, imageLoading, 'loading')

        vnode.context.$nextTick(() => {
            Listeners.push({
                bindType: binding.arg,
                attempt: 0,
                parentEl: parentEl,
                el: el,
                error: imageError,
                src: imageSrc
            })

            if (Listeners.length > 0 && !Init.hasbind) {
                Init.hasbind = true
                onListen(window, true)
            }
            if (parentEl) {
                onListen(parentEl, true)
            }
            lazyLoadHandler()
        })
    }

    Vue.directive('lazy', {
        bind: addListener,
        update: addListener,
        componentUpdated: lazyLoadHandler,
        unbind: componentWillUnmount
    })
}

当你看到这,我要谢谢你,这篇博客零零散散写了好几回,搁置了小一年了。。。不知道在想啥。。。什么什么都怎么写的都有点乱了。。。烂尾了。。。实在抱歉,下不为例~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JQuery之图片懒加载

    由于现在很多的网站都带有大量的图片,而图片的加载又会特别的慢,特别是在移动端,懒加载就显的特别重要了,说白了就是按需加载,用户要看到哪里就显示哪里,下面来记录一...

    越陌度阡
  • 小程序之图片懒加载

    懒加载,前端人都知道的一种性能优化方式,简单的来说,只有当图片出现在浏览器的可视区域内时,才设置图片正真的路径,让图片显示出来。这就是图片懒加载。

    前端黑板报
  • 原生JS实现图片懒加载

    田田田
  • Vue图片懒加载之lazyload插件使用

    当内容没有加载完的时候,用户体验不是很好,这时候,可以使用lazyload这个插件,提升用户体验,使用方法特别简单易用

    小周sri的码农
  • 前端-原生JS实现最简单的图片懒加载

    懒加载其实就是延迟加载,是一种对网页性能优化的方式,比如当访问一个页面的时候,优先显示可视区域的图片而不一次性加载所有图片,当需要显示的时候再发送图片请求,避免...

    grain先森
  • 深入理解图片和框架的原生懒加载功能

    看到本文标题你会问「懒加载是什么东西?」CSS-Tricks 网站中有非常多的探讨懒加载的文章,其中有一篇非常详尽的《用 JavaScript 花式实现懒加载的...

    ConardLi
  • WordPress 中部署真正的懒加载(Lazy Load)

    不少WordPress 主题(包括DeveWork.com目前的主题)都有部署jquery 插件“懒加载”(Lazy Load),但其实很多都不能产生真正的懒加...

    Jeff
  • 见过懒加载吗?

    我不是费圆
  • 页面提高性能利器_懒加载

    用户5521279
  • 彻底玩转图片懒加载及底层实现原理

    图片懒加载其实已经是一个近乎“烂大街”的词语了,在大大小小的面试中也会被频繁的问到,我在之前的面试中也被问到了图片懒加载的原因、实现方式及底层原理,但由于自己平...

    前端森林
  • webview 跟客户端的适配问题

    我们APP中经常存在显示网页会有网页底部留有大量空白,显示网页速度要一两秒或者更久时间的问题。

    魔王卷子
  • 元编程实现优雅的懒加载

    计算机行业发展了这么多年,出现过无数的专业名词,有的古老名词过时了并被人们淡忘,但有的古老名词不仅没过时,还从本身的狭义概念上升到广义的哲学概念。

    Jean
  • VUE项目性能优化实践——通过懒加载提升页面响应速度

    最近我司因业务需求,需要在一个内部数据分析平台集成在线Excel功能,既然我们自己就是做开发工具的,所以目光自然就落在了我司自研的前端表格产品上。

    葡萄城控件
  • 【Web技术】771- 图片懒加载从简单到复杂

    图片懒加载是一个很重要的前端性能优化手段。这篇文章将从懒加载的最简单场景开始介绍,逐步增加复杂度,希望能讲清楚常见的图片懒加载场景及在该场景下对应的解决办法,也...

    pingan8787
  • 续:WordPress 文章图片部署真正的懒加载(Lazy Load)

    几天前分享了《WordPress 中部署真正的懒加载(Lazy Load)》一文,教会了大家在WordPress 中的两个地方部署懒加载:缩略图、头像图片。今天...

    Jeff
  • 曾经热爱的Chrome,让我失业了

    据我了解,很多前端从业者已经将 Google Chrome设置为他们电脑上默认的浏览器了。

    闰土大叔
  • 琐碎的JS性能优化

    4、小图使用base64。虽然base64编码的大小比原图大一些,但是可以减少http请求。

    Cloud-Cloudys
  • 懒加载

    小胖
  • 实现一个Vue自定义指令懒加载

    当我们向下滚动的时候图片资源才被请求到,这也就是我们本次要实现的效果,进入页面的时候,只请求可视区域的图片资源这也就是懒加载。

    木子星兮

扫码关注云+社区

领取腾讯云代金券