Vue3 实现高效图片懒加载与预览功能详解
一、图片懒加载的实现
1.1 原理简介
懒加载的核心原理是:
- 监听滚动事件:检测用户滚动页面时,哪些图片进入了可视区域。
- 动态加载图片:当图片进入可视区域时,动态地将图片的
src属性从占位图替换为实际的图片URL。
1.2 实现步骤
- 创建Vue组件:
首先,创建一个名为
LazyImage.vue的Vue组件。
<template>
<img :src="loaded ? realSrc : placeholder" @load="handleLoad" />
</template>
<script setup>
import { ref, onMounted } from 'vue';
const props = defineProps({
src: String,
placeholder: {
type: String,
default: 'path/to/placeholder.png'
}
});
const loaded = ref(false);
const realSrc = ref('');
const handleLoad = () => {
loaded.value = true;
};
const loadImage = () => {
const img = new Image();
img.onload = () => {
realSrc.value = props.src;
};
img.src = props.src;
};
onMounted(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadImage();
observer.unobserve(entry.target);
}
});
});
observer.observe(document.querySelector('img'));
});
</script>
- 使用组件:
在其他Vue组件中引入并使用
LazyImage组件。
<template>
<div>
<LazyImage src="path/to/your/image.jpg" />
</div>
</template>
<script setup>
import LazyImage from './components/LazyImage.vue';
</script>
二、图片预览功能的实现
2.1 原理简介
- 模态框展示:点击图片时,弹出一个模态框展示大图。
- 附加功能:在模态框中添加旋转、缩放等按钮,通过CSS和JavaScript实现相应的功能。
2.2 实现步骤
- 创建预览组件:
创建一个名为
ImagePreview.vue的Vue组件。
<template>
<div v-if="visible" class="preview-modal">
<div class="preview-content">
<img :src="currentImage" :style="imageStyle" />
<button @click="rotateImage">旋转</button>
<button @click="closePreview">关闭</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps({
images: Array,
initialIndex: {
type: Number,
default: 0
}
});
const visible = ref(false);
const currentImage = ref('');
const rotationAngle = ref(0);
const openPreview = (index) => {
visible.value = true;
currentImage.value = props.images[index];
};
const closePreview = () => {
visible.value = false;
rotationAngle.value = 0;
};
const rotateImage = () => {
rotationAngle.value = (rotationAngle.value + 90) % 360;
};
const imageStyle = computed(() => ({
transform: `rotate(${rotationAngle.value}deg)`
}));
defineExpose({
openPreview
});
</script>
<style>
.preview-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.preview-content img {
max-width: 90%;
max-height: 90%;
}
</style>
- 使用预览组件:
在其他Vue组件中引入并使用
ImagePreview组件。
<template>
<div>
<img v-for="(img, index) in images" :key="img" :src="img" @click="preview.openPreview(index)" />
<ImagePreview ref="preview" :images="images" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import ImagePreview from './components/ImagePreview.vue';
const images = ref(['path/to/image1.jpg', 'path/to/image2.jpg']);
const preview = ref(null);
</script>
三、结合懒加载与预览功能
- 修改
LazyImage组件: 在LazyImage组件中添加点击事件,触发预览。
<template>
<img :src="loaded ? realSrc : placeholder" @load="handleLoad" @click="handleClick" />
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { usePreview } from './usePreview'; // 假设这是预览功能的组合式API
const props = defineProps({
src: String,
placeholder: {
type: String,
default: 'path/to/placeholder.png'
}
});
const { loaded, realSrc, handleLoad, loadImage } = useLazyLoad(props);
const { openPreview } = usePreview();
const handleClick = () => {
openPreview(props.src);
};
onMounted(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadImage();
observer.unobserve(entry.target);
}
});
});
observer.observe(document.querySelector('img'));
});
</script>
- 创建组合式API:
创建
useLazyLoad和usePreview两个组合式API,分别用于懒加载和预览功能。
// useLazyLoad.js
import { ref } from 'vue';
export function useLazyLoad(props) {
const loaded = ref(false);
const realSrc = ref('');
const handleLoad = () => {
loaded.value = true;
};
const loadImage = () => {
const img = new Image();
img.onload = () => {
realSrc.value = props.src;
};
img.src = props.src;
};
return { loaded, realSrc, handleLoad, loadImage };
}
// usePreview.js
import { ref } from 'vue';
export function usePreview() {
const visible = ref(false);
const currentImage = ref('');
const rotationAngle = ref(0);
const openPreview = (image) => {
visible.value = true;
currentImage.value = image;
};
const closePreview = () => {
visible.value = false;
rotationAngle.value = 0;
};
const rotateImage = () => {
rotationAngle.value = (rotationAngle.value + 90) % 360;
};
return { visible, currentImage, rotationAngle, openPreview, closePreview, rotateImage };
}