看了 ElImage 的源码后,改进了之前用 IntersectionObserver 实现的图片懒加载指令。在我的实现中 IntersectionObserver 的主要问题是不支持 rootMargin,只能在图片进入视口时加载。而 ElImage 的实现方案是判断图片是否进入滚动容器,在这个基础上我们略加配置即可实现 topMargin 预加载的功能。
import {
useThrottleFn,
useEventListener
} from '@vueuse/core'
function isScroll(el) {
let overflow = el.style['overflow'];
if (!overflow) {
overflow = document.defaultView.getComputedStyle(el)['overflow'];
}
return ['auto', 'scroll', 'overlay'].some((v) => v === overflow)
}
function getScrollContainer(el) {
let parent = el;
while (parent) {
if ([window, document, document.documentElement].includes(parent)) {
return window;
}
if (isScroll(parent)) {
return parent;
}
parent = parent.parentElement;
}
return parent;
}
function isInContainer(el, container, marginTop = 0) {
let rect;
const {
top,
left,
right,
bottom
} = el.getBoundingClientRect();
if (container instanceof Element) {
rect = container.getBoundingClientRect();
} else {
rect = {
top: 0,
left: 0,
bottom: window.innerHeight,
right: window.innerWidth
}
}
return (
top - marginTop < rect.bottom &&
bottom > rect.top &&
left < rect.right &&
right > rect.left
)
}
export default {
mounted(el, bindings) {
const {
top,
src
} = bindings.value;
const container = getScrollContainer(el);
const loadImageHandler = () => {
if (isInContainer(el, container, parseInt(top) || 0)) {
el.setAttribute('src', src);
stopListener()
}
}
const stopListener = useEventListener(container, 'scroll', useThrottleFn(loadImageHandler))
setTimeout(loadImageHandler, 50);
},
}
<template>
<div class="thumb" :class="{ video: isVideo(post.file_url) }">
<img
src="/src/assets/image/loading.gif"
:alt="`post. ${post.id}`"
v-lazy="img"
/>
</div>
</template>
<script>
export default {
props: {
post: {
type: Object,
},
},
methods: {
isVideo(url) {
return url !== "" && (url.endsWith("webm") || url.endsWith("mp4"));
},
},
computed: {
img() {
return {
src: this.post.preview_url,
top: 250,
};
},
},
};
</script>
<style scoped>
.thumb {
height: 200px;
width: 200px;
cursor: pointer;
overflow: hidden;
transition: transform 0.2s ease;
/* box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); */
}
.thumb:hover {
transform: translateY(-5px);
}
.thumb.video {
outline: 2px solid blue;
}
.thumb > img {
object-fit: contain;
width: 100%;
display: block;
height: 100%;
}
</style>