101 lines
2.3 KiB
JavaScript
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;
|
|
};
|
|
}
|
|
}; |