创建 directives
文件,新建 vSlideIn.js
指令文件
/**
* @Author : xlsir
* @Description: vSlideIn
* @Create_time: 2025-08-03 08:18
*/
const DISTANCE = 100
const DURATION = 500
const map = new WeakMap()
const ob = new IntersectionObserver(entries => {
for (const entry of entries) {
if (entry.isIntersecting) {
//出现在视口
const animation = map.get(entry.target)
animation && animation.play()
ob.unobserve(entry.target)
}
}
})
// 判断元素有没有在视口
function isBelowViewport(el) {
const rect = el.getBoundingClientRect()
return rect.top - window.innerWidth > 0
}
export default {
mounted(el) {
if (!isBelowViewport(el)) return
const animation = el.animate([
{
transform: `translateY(${DISTANCE}px)`,
opacity: 0.5,
}, {
transform: `translateY(0)`,
opacity: 1,
}], {
duration: DURATION,
easing: 'ease-out',
fill: 'forwards'
});
animation.pause()
map.set(el, animation)
ob.observe(el)
},
unmounted(el) {
ob.unobserve(el)
}
}
如何使用
main.js注册
import { createApp } from 'vue' import App from './App.vue' import vSideIn from '@/directives/vSlideIn.js' //引入指令文件 const app = createApp(App) app.directive('side-in',vSideIn) app.mount('#app')
文件使用
<template> <div class="content"> <div v-side-in class="items" v-for="i in 20">{{i}}</div> </div> </template> <style scoped> .content{ width: 900px; margin: 0 auto; } .items{ width: 100%; aspect-ratio: 2/1; margin: 5vw 0; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; font-size: 10vw; color: #fff; } .items:nth-child(3n+1) { background-color: orange; } .items:nth-child(3n+2) { background-color: red; } .items:nth-child(3n+3) { background-color: green; } </style>