From 865a10c7e51b0308c99d7387ab5740c7843e31b1 Mon Sep 17 00:00:00 2001 From: haoziqaq <357229046@qq.com> Date: Wed, 23 Dec 2020 13:13:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui/lazy):=20=E5=A2=9E=E5=8A=A01px=E5=83=8F?= =?UTF-8?q?=E7=B4=A0=E5=8D=A0=E4=BD=8D=20=E5=A2=9E=E5=8A=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87URL=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit affects: @varlet/ui --- packages/varlet-ui/src/lazy/example/index.vue | 19 ++-- packages/varlet-ui/src/lazy/index.ts | 88 ++++++++++++------- packages/varlet-ui/src/utils/shared.ts | 29 ++++++ 3 files changed, 92 insertions(+), 44 deletions(-) diff --git a/packages/varlet-ui/src/lazy/example/index.vue b/packages/varlet-ui/src/lazy/example/index.vue index 30002416bab..4facb48d58d 100644 --- a/packages/varlet-ui/src/lazy/example/index.vue +++ b/packages/varlet-ui/src/lazy/example/index.vue @@ -3,13 +3,10 @@ @@ -28,34 +25,34 @@ export default defineComponent({ const images = reactive([ { id: 1, - url: 'https://cn.vuejs.org/images/logo.png', + url: 'https://ddtask.oss-cn-shanghai.aliyuncs.com/ddtask/201208ysl/images/1606982343202.jpg?Expires=1608703112&OSSAccessKeyId=TMP.3Kj8QoTDYVpdcwERRbtMRUh7wkoZvgG25VLcqMaqzYFdcqECUoupXbaFYu36z8tcVvCgwHxUo2RF1xdB46MJpNesxSzKsv&Signature=tmLF4Y65KgbR%2FeUXdZTGSMArxIo%3D&versionId=CAEQFBiBgMDNormesRciIGU5YmQ5MWQwZjZhZTQxNGY5Nzk5YjM1ZDRhY2Q0NzYw&response-content-type=application%2Foctet-stream', }, { id: 2, - url: 'https://cn.vuejs.org/imagessss/logo.png', + url: 'https://ddtask.oss-cn-shanghai.aliyuncs.com/ddtask/201208ysl/images/1606982343202.jpg?Expires=1608703112&OSSAccessKeyId=TMP.3Kj8QoTDYVpdcwERRbtMRUh7wkoZvgG25VLcqMaqzYFdcqECUoupXbaFYu36z8tcVvCgwHxUo2RF1xdB46MJpNesxSzKsv&Signature=tmLF4Y65KgbR%2FeUXdZTGSMArxIo%3D&versionId=CAEQFBiBgMDNormesRciIGU5YmQ5MWQwZjZhZTQxNGY5Nzk5YjM1ZDRhY2Q0NzYw&response-content-type=application%2Foctet-stream', }, { id: 3, - url: 'https://cn.vuejs.org/images/logo.png', + url: 'https://ddtask.oss-cn-shanghai.aliyuncs.com/ddtask/201208ysl/images/1606982343202.jpg?Expires=1608703112&OSSAccessKeyId=TMP.3Kj8QoTDYVpdcwERRbtMRUh7wkoZvgG25VLcqMaqzYFdcqECUoupXbaFYu36z8tcVvCgwHxUo2RF1xdB46MJpNesxSzKsv&Signature=tmLF4Y65KgbR%2FeUXdZTGSMArxIo%3D&versionId=CAEQFBiBgMDNormesRciIGU5YmQ5MWQwZjZhZTQxNGY5Nzk5YjM1ZDRhY2Q0NzYw&response-content-type=application%2Foctet-stream', }, { id: 4, - url: 'https://cn.vuejs.org/images/logo.png', + url: 'https://ddtask.oss-cn-shanghai.aliyuncs.com/ddtask/201208ysl/images/1606982343202.jpg?Expires=1608703112&OSSAccessKeyId=TMP.3Kj8QoTDYVpdcwERRbtMRUh7wkoZvgG25VLcqMaqzYFdcqECUoupXbaFYu36z8tcVvCgwHxUo2RF1xdB46MJpNesxSzKsv&Signature=tmLF4Y65KgbR%2FeUXdZTGSMArxIo%3D&versionId=CAEQFBiBgMDNormesRciIGU5YmQ5MWQwZjZhZTQxNGY5Nzk5YjM1ZDRhY2Q0NzYw&response-content-type=application%2Foctet-stream', },{ id: 5, - url: 'https://cn.vuejs.org/images/logo.png', + url: 'https://ddtask.oss-cn-shanghai.aliyuncs.com/ddtask/201208ysl/images/1606982343202.jpg?Expires=1608703112&OSSAccessKeyId=TMP.3Kj8QoTDYVpdcwERRbtMRUh7wkoZvgG25VLcqMaqzYFdcqECUoupXbaFYu36z8tcVvCgwHxUo2RF1xdB46MJpNesxSzKsv&Signature=tmLF4Y65KgbR%2FeUXdZTGSMArxIo%3D&versionId=CAEQFBiBgMDNormesRciIGU5YmQ5MWQwZjZhZTQxNGY5Nzk5YjM1ZDRhY2Q0NzYw&response-content-type=application%2Foctet-stream', } ]) return { images, fix() { - images[1].url = 'https://cn.vuejs.org/images/logo.png' + images[1].url = 'https://ddtask.oss-cn-shanghai.aliyuncs.com/ddtask/201208ysl/images/1606982343202.jpg?Expires=1608703112&OSSAccessKeyId=TMP.3Kj8QoTDYVpdcwERRbtMRUh7wkoZvgG25VLcqMaqzYFdcqECUoupXbaFYu36z8tcVvCgwHxUo2RF1xdB46MJpNesxSzKsv&Signature=tmLF4Y65KgbR%2FeUXdZTGSMArxIo%3D&versionId=CAEQFBiBgMDNormesRciIGU5YmQ5MWQwZjZhZTQxNGY5Nzk5YjM1ZDRhY2Q0NzYw&response-content-type=application%2Foctet-stream' }, push() { images.push({ id: Date.now(), - url: 'https://cn.vuejs.org/images/logo.png' + url: 'https://ddtask.oss-cn-shanghai.aliyuncs.com/ddtask/201208ysl/images/1606982343202.jpg?Expires=1608703112&OSSAccessKeyId=TMP.3Kj8QoTDYVpdcwERRbtMRUh7wkoZvgG25VLcqMaqzYFdcqECUoupXbaFYu36z8tcVvCgwHxUo2RF1xdB46MJpNesxSzKsv&Signature=tmLF4Y65KgbR%2FeUXdZTGSMArxIo%3D&versionId=CAEQFBiBgMDNormesRciIGU5YmQ5MWQwZjZhZTQxNGY5Nzk5YjM1ZDRhY2Q0NzYw&response-content-type=application%2Foctet-stream' }) }, pop() { diff --git a/packages/varlet-ui/src/lazy/index.ts b/packages/varlet-ui/src/lazy/index.ts index 37e8cee5bea..025c08c05b8 100644 --- a/packages/varlet-ui/src/lazy/index.ts +++ b/packages/varlet-ui/src/lazy/index.ts @@ -1,7 +1,13 @@ import { App, Directive, Plugin } from 'vue' import { DirectiveBinding } from '@vue/runtime-core' import { inViewport } from '../utils/elements' -import { checkIntersectionObserverAPI, createInViewportObserver, removeItem, throttle } from '../utils/shared' +import { + CacheInstance, + checkIntersectionObserverAPI, createCache, + createInViewportObserver, + removeItem, + throttle +} from '../utils/shared' interface LazyOptions { loading?: string @@ -18,19 +24,22 @@ type Lazy = LazyOptions & { attemptLock: boolean } -type LazyHTMLElement = HTMLElement & { lazy: Lazy } +type LazyHTMLElement = HTMLElement & { _lazy: Lazy } const BACKGROUND_IMAGE_ARG_NAME = 'background-image' const LAZY_LOADING = 'lazy-loading' const LAZY_ERROR = 'lazy-error' const LAZY_ATTEMPT = 'lazy-attempt' const EVENTS = ['resize', 'animationend', 'transitionend', 'touchmove', 'scroll'] +const PIXEL = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==' const lazyElements: LazyHTMLElement[] = [] +const imageCache: CacheInstance = createCache(100) + let defaultLazyOptions: LazyOptions = { - loading: '', - error: '', + loading: PIXEL, + error: PIXEL, attempt: 3, throttleWait: 300, } @@ -42,21 +51,21 @@ let observer: IntersectionObserver | null = null const useIntersectionObserverAPI: boolean = checkIntersectionObserverAPI() function setLoading(el: LazyHTMLElement) { - el.lazy.loading - ? el.lazy.arg === BACKGROUND_IMAGE_ARG_NAME - ? el.style.backgroundImage = `url(${el.lazy.loading})` - : el.setAttribute('src', el.lazy.loading) + el._lazy.loading + ? el._lazy.arg === BACKGROUND_IMAGE_ARG_NAME + ? el.style.backgroundImage = `url(${el._lazy.loading})` + : el.setAttribute('src', el._lazy.loading) : null !useIntersectionObserverAPI && checkAll() } function setError(el: LazyHTMLElement) { - if (el.lazy.error) { - if (el.lazy.arg === BACKGROUND_IMAGE_ARG_NAME) { - el.style.backgroundImage = `url(${el.lazy.error})` + if (el._lazy.error) { + if (el._lazy.arg === BACKGROUND_IMAGE_ARG_NAME) { + el.style.backgroundImage = `url(${el._lazy.error})` } else { - el.setAttribute('src', el.lazy.error) + el.setAttribute('src', el._lazy.error) } } @@ -65,7 +74,7 @@ function setError(el: LazyHTMLElement) { } function setSuccess(el: LazyHTMLElement, attemptSRC: string) { - el.lazy.arg === BACKGROUND_IMAGE_ARG_NAME + el._lazy.arg === BACKGROUND_IMAGE_ARG_NAME ? el.style.backgroundImage = `url(${attemptSRC})` : el.setAttribute('src', attemptSRC) @@ -86,10 +95,10 @@ function unbindEvents() { } function buildAttemptSRC(el: LazyHTMLElement): string { - const { src } = el.lazy + const { src } = el._lazy - if (el.lazy.currentAttempt === 0) { - el.lazy.currentAttempt++ + if (el._lazy.currentAttempt === 0) { + el._lazy.currentAttempt++ return src } @@ -98,7 +107,7 @@ function buildAttemptSRC(el: LazyHTMLElement): string { const params: string = index > -1 ? src.slice(src.indexOf('?')) : '' const searchParams = new URLSearchParams(params) - searchParams.set('lazyAttempt', String(++el.lazy.currentAttempt)) + searchParams.set('lazyAttempt', String(++el._lazy.currentAttempt)) return `${url}?${searchParams.toString()}` } @@ -110,7 +119,7 @@ function mergeLazyOptions(el: LazyHTMLElement, binding: DirectiveBinding attempt: el.getAttribute(LAZY_ATTEMPT) ? Number(el.getAttribute(LAZY_ATTEMPT)) : defaultLazyOptions.attempt } - el.lazy = { + el._lazy = { src: binding.value, arg: binding.arg, currentAttempt: 0, @@ -118,33 +127,44 @@ function mergeLazyOptions(el: LazyHTMLElement, binding: DirectiveBinding ...lazyInnerOptions } - defaultLazyOptions.filter?.(el.lazy) + defaultLazyOptions.filter?.(el._lazy) } -function attemptLoad(el: LazyHTMLElement) { - if (el.lazy.attemptLock === true) { - return - } - el.lazy.attemptLock = true - - setLoading(el) - - const attemptSRC: string = buildAttemptSRC(el) - +function createImage(el: LazyHTMLElement, attemptSRC: string) { const image: HTMLImageElement = new Image() image.src = attemptSRC image.addEventListener('load', () => { - el.lazy.attemptLock = false + el._lazy.attemptLock = false + + imageCache.add(attemptSRC) setSuccess(el, attemptSRC) }) image.addEventListener('error', () => { - el.lazy.attemptLock = false - ;(el.lazy.currentAttempt as number) >= (el.lazy.attempt as number) + el._lazy.attemptLock = false + + ;(el._lazy.currentAttempt as number) >= (el._lazy.attempt as number) ? setError(el) : attemptLoad(el) }) } +function attemptLoad(el: LazyHTMLElement) { + if (el._lazy.attemptLock === true) { + return + } + el._lazy.attemptLock = true + + const attemptSRC: string = buildAttemptSRC(el) + if (imageCache.has(attemptSRC)) { + setSuccess(el, attemptSRC) + el._lazy.attemptLock = false + return + } + + setLoading(el) + createImage(el, attemptSRC) +} + function check(el: LazyHTMLElement) { inViewport(el) && attemptLoad(el) } @@ -172,6 +192,8 @@ function observe(el: LazyHTMLElement) { } function mounted(el: LazyHTMLElement, binding: DirectiveBinding) { + !el.getAttribute('src') && el.setAttribute('src', PIXEL) + mergeLazyOptions(el, binding) if (useIntersectionObserverAPI) { @@ -187,7 +209,7 @@ function unmounted(el: LazyHTMLElement) { } function diff(el: LazyHTMLElement, binding: DirectiveBinding): boolean { - const { src, arg, attempt, loading, error } = el.lazy + const { src, arg, attempt, loading, error } = el._lazy return src !== binding.value || arg !== binding.arg || attempt !== Number(el.getAttribute(LAZY_ATTEMPT)) || diff --git a/packages/varlet-ui/src/utils/shared.ts b/packages/varlet-ui/src/utils/shared.ts index fc1026c56ac..6d32e5e693a 100644 --- a/packages/varlet-ui/src/utils/shared.ts +++ b/packages/varlet-ui/src/utils/shared.ts @@ -1,3 +1,10 @@ +export interface CacheInstance { + cache: T[] + has(key: T) + add(key: T) + remove(key: T) +} + export const isString = (val: unknown): val is string => typeof val === 'string' export const isBaseObject = (val: unknown) => Object.prototype.toString.call(val) === '[object Object]' @@ -44,3 +51,25 @@ export const createInViewportObserver = (handler: (el: T) => void): Intersect }) }) } + +export const createCache = (max: number): CacheInstance => { + const cache: T[] = [] + + return { + cache, + has(key: T) { + return cache.includes(key) + }, + add(key: T) { + if (this.has(key)) { + return + } + + this.cache.length === max && cache.shift() + this.cache.push(key) + }, + remove(key: T) { + this.has(key) && removeItem(this.cache, key) + } + } +}