+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 学习分享|利用Vue自定义指令(directives)实现全站动画效果
+
+
+
+
+
+
+
+
+
最近一直在学习 React 高阶知识,因此对于主页的开发再一次停滞了,主要也是一段时间内没有找到什么灵感,今天空闲时间打开又添加了一些另外的功能,其中包括了折腾了好久的主页加载 Spring (不是那个 Spring 啦)弹簧效果和模糊效果。
+
+
+
最后的效果在 Grtsinry43 的个人主页 (最近还会考虑继续更新,哭)
+
环境准备
这里我的网站是使用 Nuxt.js 开发的,使用了"nuxt": "^3.12.4",
于是就可以使用 Vue.js 的所有语法和写法,当然也包括自定义指令啦!这就为我们的集成添加了可行性
+
前置知识
首先我们要知道 Vue 中的指令是类似v-xxx
的形式,比如我们平时常用的 v-model
v-for
等等都是其提供的指令,当然也为我们保留了自定义的能力。
+
问题引入
在标准 Vue/Vue+Vite 项目中,其往往位于 /directives
路径,在我们自定义之后在 main.js/main.ts
文件中引入即可
+
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue'; import App from './App.vue'; import scrollSpring from '@/directives/scrollSpring';
const app = createApp(App);
app.directive('scroll-spring', scrollSpring);
app.mount('#app');
|
+
+
而在 Nuxt.js项目中,其主 app
对象创建与挂载,以及 SSR 实现等都是由框架统一管理,为了引入里定自定义指令,我们要借助 nuxt 强大的插件系统。
+
问题解决
我们可以参考对应的文档: Vue指令
+
其给出了这样一段示例
+
1 2 3 4 5 6 7 8 9 10 11
| export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.directive('focus', { mounted (el) { el.focus() }, getSSRProps (binding, vnode) { // 你可以在这里提供SSR特定的props return {} } }) })
|
+
+
由于我们无需单独处理 SSR 部分,直接引入插件注册指令即可,Nuxt 会自动扫描并加载 /plugins
路径的文件,无需手动添加。
+
实现这段动画的大致思路就是,首先所有元素默认 opacity
为 0,并且有向下的位移,当元素移动到视口内即添加标签设置为可见,并恢复位置,当然也可以配合 filter blur 等实现模糊渐显的效果,于是写好如下 css
+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| /* 初始状态,元素处于下方且不可见,并带有模糊效果 */ .scroll-item { opacity: 0; filter: blur(10px); /* 元素模糊 25px */ transform: translateY(20px); /* 元素初始位移 20px */ transition: transform 0.5s ease-out, opacity 0.5s ease-out, filter 0.5s ease-out; /* 为 filter 添加动画 */ }
/* 当元素进入视口时,透明度变为 1,模糊度变为 0,且上移回原位 */ .scroll-in { opacity: 1; filter: blur(0); /* 模糊效果消失 */ transform: translateY(0); /* 元素回到原始位置 */ transition: transform 0.5s ease-out, opacity 0.5s ease-out, filter 0.5s ease-out; /* 确保 filter 也有动画 */ }
|
+
+
有了思路之后就写好对应的指令 js
/plugins/scrollSpring.js
+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| // 导出一个 Nuxt 插件 export default defineNuxtPlugin((nuxtApp) => { // 定义一个自定义指令 'scroll-spring' nuxtApp.vueApp.directive('scroll-spring', { // 当元素被挂载到 DOM 中时调用的钩子 mounted(el) { // 设置 Intersection Observer 的选项 const options = { root: null, // 使用浏览器视口作为根元素 rootMargin: '0px', // 根元素的边距 threshold: 0.1 // 交叉的阈值,当 10% 的目标元素在视口内时触发回调 };
// 观察者回调函数 const callback = (entries) => { entries.forEach(entry => { // 如果目标元素与视口相交 if (entry.isIntersecting) { // 添加动画类 el.classList.add('scroll-in'); } else { // 移出视口时移除动画类 el.classList.remove('scroll-in'); } }); };
// 创建一个 Intersection Observer 实例,并传入回调函数和选项 const observer = new IntersectionObserver(callback, options); // 开始观察当前元素 observer.observe(el); }, // 当元素从 DOM 中卸载时调用的钩子 unmounted(el) { // 创建一个空的 Intersection Observer 实例 const observer = new IntersectionObserver(() => {}); // 停止观察当前元素 observer.unobserve(el); } }); });
|
+
+
这里我们利用 Intersection Observer
,当元素进入视口触发回调添加类名,移出视口则去掉类名
+
接下来我们只要在对应的元素引入就可以啦!
注意 scroll-item
和 v-scroll-spring
缺一不可,前者负责决定开始状态,后者是动画和结束效果
+
这里简单贴一个代码片段,也可以参考我主页对应的仓库~
/pages/index.vue
+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <div class="slogan font-jb-mono scroll-item" v-scroll-spring> <p>Coding,</p> <p>build a better world</p> <p>together!</p> </div> <span class="slogan-cn scroll-item" v-if="locale === 'zh'" v-scroll-spring>{{ t('slogan.cn') }}</span> <br/> <div class="button-container scroll-item" v-scroll-spring> <UButton to="https://github.com/grtsinry43" target="_blank" icon="i-grommet-icons:github" style="vertical-align: -4px" class="btn-item github-link bg-blue-400 text-black dark:bg-blue-800 dark:text-white"> {{ t('buttons.github') }} </UButton> <UButton :label="t('buttons.learningLog')" color="gray" class="btn-item scroll-item" v-scroll-spring> <template #trailing> <UIcon name="i-heroicons-arrow-right-20-solid" class="w-5 h-5 btn-more-icon"/> </template> </UButton> <UButton color="gray" class="btn-item scroll-item" disabled v-scroll-spring> {{ t('buttons.resume') }} </UButton> </div>
|
+
+
最后效果
![https://blogoss.grtsinry43.com/uploads/24/10/dfd9b723931112b166843ff4b28d68fa.gif/improved](https://blogoss.grtsinry43.com/uploads/24/10/dfd9b723931112b166843ff4b28d68fa.gif/improved)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + 条评论 + +
+ +