zoukankan      html  css  js  c++  java
  • 懒就是生产力之图片懒加载


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

    什么是懒加载

    懒加载也叫延迟加载,本质上就是按需加载,即只有当图片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
        })
    }
    

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

  • 相关阅读:
    轻松实现内容的无缝平滑滚动
    世界“新七大奇迹”揭晓 中国长城成功入选
    Blog_Backup:绿色博客备份工具
    【Vista中系统准备工具存在隐患,自动清除用户资料】
    Oracle大连全球支持中心运营就在我们公司前面!
    ASP.NET2.0下使用AJAX调用Webservice的方法
    配合AJAX天气预报的WebService
    Google兑现承诺你可以从google上删除你的搜索记录了
    全体注意:@live.com 形势不妙,可能三周内被收回
    国内软件联盟抨击微软以非正规手段推文档标准
  • 原文地址:https://www.cnblogs.com/heioray/p/9661492.html
Copyright © 2011-2022 走看看