主题
指令
图片懒加载
html
<img v-lazy="imageSrc" :alt="imageAlt" class="lazy-image" />js
/**
* 图片懒加载指令 (v-lazy)
*
* 该指令用于实现图片的懒加载功能,提高页面性能和用户体验。
* 原理:使用 IntersectionObserver API 监测图片元素是否进入视口,
* 只有当图片进入可视区域时才会加载真实图片,从而减少不必要的网络请求。
*
* 特点:
* 1. 自动监测元素是否进入视口
* 2. 提供加载占位图和加载失败的错误图
* 3. 支持加载动画效果
* 4. 使用现代的 IntersectionObserver API,性能更好
*
* 使用方法:v-lazy="图片路径"
* 示例:<img v-lazy="'/path/to/image.jpg'" alt="懒加载图片" />
*/
import placeholder from '@/assets/vue.svg';
export default {
/**
* 指令挂载时的处理函数
*
* @param {HTMLElement} el - 指令绑定的元素,通常是<img>标签
* @param {Object} binding - 包含指令相关信息的对象
* @param {string} binding.value - 指令的值,这里是图片的真实路径
*/
mounted(el, binding) {
/**
* 创建 IntersectionObserver 实例
* 用于监测元素是否进入视口
*
* @param {Function} callback - 当被观察元素的可见性变化时调用的函数
* @param {Object} options - 观察器的配置选项
*/
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
// 当元素进入视口时
if (entry.isIntersecting) {
// 获取目标元素和图片真实路径
const img = entry.target;
const src = binding.value;
// 创建一个新的图片对象来预加载
// 这样可以在图片完全加载好之后再显示,避免显示过程中的闪烁
const loadImg = new Image();
loadImg.src = src;
/**
* 图片加载成功的处理
* 1. 将预加载的图片路径赋值给实际显示的图片元素
* 2. 添加已加载的CSS类,可用于添加过渡动画
*/
loadImg.onload = () => {
// 图片加载完成后设置src
img.src = src;
img.classList.add('lazy-loaded');
};
/**
* 图片加载失败的处理
* 显示一个默认的错误占位图
*/
loadImg.onerror = () => {
// 图片加载失败时可以设置一个默认图片
img.src = '/src/assets/images/error.png';
};
// 图片已经开始加载,停止观察该元素
// 这样可以节省性能,避免重复触发加载
observer.unobserve(img);
}
});
},
{
// 配置项
rootMargin: '0px', // 根元素的外边距,可用于提前或延迟触发
threshold: 0.1, // 当元素10%进入视口时触发,可根据需要调整
}
);
/**
* 初始化处理
* 1. 设置占位图
* 2. 开始观察元素
*
* 注意:这里只处理IMG标签,如果要支持其他元素(如背景图),
* 需要扩展这部分逻辑
*/
if (el.tagName === 'IMG') {
// 设置初始占位图,在真实图片加载前显示
el.src = placeholder;
// 开始观察元素
observer.observe(el);
}
},
};js
import lazyLoad from './lazyLoad.js';
import draggable from './draggable.js';
export default {
install(app) {
// 注册图片懒加载指令
app.directive('lazy', lazyLoad);
app.directive('draggable', draggable);
},
};javascript
import directives from '@/directives/index.js';
// 注册自定义指令
app.use(directives);移动端拖拽元素
vue
<template>
<div>
<div v-draggable class="box"></div>
</div>
</template>
<script setup></script>
<route lang="json5">
{
meta:{
title: '拖拽指令',
order: 4
},
}
</route>
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: red;
}
</style>js
// draggable-float.js
export default {
mounted(el) {
// 初始化默认位置
const resetPosition = () => {
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
const elementWidth = el.offsetWidth;
const elementHeight = el.offsetHeight;
el.style.left = screenWidth - elementWidth - 10 + 'px'; // 右侧贴边(留10px间距)
el.style.top = (screenHeight - elementHeight) / 2 + 'px'; // 居中对齐
};
// 暴露给外部使用
el.resetPosition = resetPosition;
// 初始位置
resetPosition();
el.style.position = 'absolute';
el.style.cursor = 'grab';
el.style.transition = 'left 0.3s ease-out, top 0.3s ease-out';
// 事件处理函数封装
const touchStartHandler = (event) => {
event.preventDefault();
el.style.transition = 'none';
const touch = event.touches[0];
const startX = touch.clientX - el.offsetLeft;
const startY = touch.clientY - el.offsetTop;
const moveHandler = (moveEvent) => {
moveEvent.preventDefault();
const moveTouch = moveEvent.touches[0];
let newX = moveTouch.clientX - startX;
let newY = moveTouch.clientY - startY;
const maxY = window.innerHeight - el.offsetHeight;
newY = Math.max(0, Math.min(newY, maxY));
el.style.left = newX + 'px';
el.style.top = newY + 'px';
};
const endHandler = () => {
document.removeEventListener('touchmove', moveHandler);
document.removeEventListener('touchend', endHandler);
const screenWidth = window.innerWidth;
const elementWidth = el.offsetWidth;
el.style.transition = 'left 0.3s ease-out, top 0.3s ease-out';
el.style.left = screenWidth - elementWidth - 10 + 'px'; // 贴右边
};
document.addEventListener('touchmove', moveHandler, { passive: false });
document.addEventListener('touchend', endHandler);
};
el._touchStartHandler = touchStartHandler; // 缓存引用,用于解绑
el.addEventListener('touchstart', touchStartHandler, { passive: false });
},
unmounted(el) {
if (el._touchStartHandler) {
el.removeEventListener('touchstart', el._touchStartHandler);
delete el._touchStartHandler;
}
},
};