2024-06-25 16:19:04 +08:00

101 lines
2.3 KiB
JavaScript

import { reactive, h, computed, ref, onMounted, onBeforeUnmount } from "vue";
export default {
props: {
src: {
type: String,
required: true
},
srcPlaceholder: {
type: String,
default:
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
},
srcset: {
type: String
},
intersectionOptions: {
type: Object,
default: () => ({})
},
usePicture: {
type: Boolean,
default: false
}
},
inheritAttrs: false,
setup(props, { attrs, slots, emit }) {
const root = ref(null);
const state = reactive({
observer: null,
intersected: false,
loaded: false
});
// Computed
const srcImage = computed(() =>
state.intersected && props.src ? props.src : props.srcPlaceholder
);
const srcsetImage = computed(() =>
state.intersected && props.srcset ? props.srcset : false
);
// Methods
const load = () => {
if (
root.value &&
root.value.getAttribute("src") !== props.srcPlaceholder
) {
state.loaded = true;
emit("load", root.value);
}
};
const error = () => emit("error", root.value);
// Hooks
onMounted(() => {
if ("IntersectionObserver" in window) {
state.observer = new IntersectionObserver((entries) => {
const image = entries[0];
if (image.isIntersecting) {
state.intersected = true;
state.observer.disconnect();
emit("intersect");
}
}, props.intersectionOptions);
state.observer.observe(root.value);
}
});
onBeforeUnmount(() => {
if ("IntersectionObserver" in window && state.observer) {
state.observer.disconnect();
}
});
return () => {
const img = h("img", {
ref: root,
src: srcImage.value,
srcset: srcsetImage.value || null, // set to null explicitly if falsy
...attrs,
class: [
attrs.class,
"v-lazy-image",
{ "v-lazy-image-loaded": state.loaded }
],
onLoad: load,
onError: error
});
return props.usePicture
? h(
"picture",
{ ref: root, onLoad: load },
state.intersected ? [slots.default, img] : [img]
)
: img;
};
}
};