利用Vue自定义指令(directives)实现全站动画效果

grtsinry43
10/13/2024
29 views
预计阅读时长 10 分钟

最近一直在学习 React 高阶知识,因此对于主页的开发再一次停滞了,主要也是一段时间内没有找到什么灵感,今天空闲时间打开又添加了一些另外的功能,其中包括了折腾了好久的主页加载 Spring (不是那个 Spring 啦)弹簧效果和模糊效果。

<!--more-->

最后的效果在 Grtsinry43 的个人主页

(最近还会考虑继续更新,哭)

环境准备

这里我的网站是使用 Nuxt.js 开发的,使用了"nuxt": "^3.12.4",于是就可以使用 Vue.js 的所有语法和写法,当然也包括自定义指令啦!这就为我们的集成添加了可行性

前置知识

首先我们要知道 Vue 中的指令是类似v-xxx的形式,比如我们平时常用的 v-model v-for 等等都是其提供的指令,当然也为我们保留了自定义的能力。

问题引入

在标准 Vue/Vue+Vite 项目中,其往往位于 /directives 路径,在我们自定义之后在 main.js/main.ts 文件中引入即可

JS
1import { createApp } from 'vue';
2import App from './App.vue';
3import scrollSpring from '@/directives/scrollSpring';
4
5const app = createApp(App);
6
7app.directive('scroll-spring', scrollSpring);
8
9app.mount('#app');

而在 Nuxt.js项目中,其主 app 对象创建与挂载,以及 SSR 实现等都是由框架统一管理,为了引入里定自定义指令,我们要借助 nuxt 强大的插件系统。

问题解决

我们可以参考对应的文档: Vue指令

其给出了这样一段示例

JS
1export default defineNuxtPlugin((nuxtApp) => {
2  nuxtApp.vueApp.directive('focus', {
3    mounted (el) {
4      el.focus()
5    },
6    getSSRProps (binding, vnode) {
7      // 你可以在这里提供SSR特定的props
8      return {}
9    }
10  })
11})

由于我们无需单独处理 SSR 部分,直接引入插件注册指令即可,Nuxt 会自动扫描并加载 /plugins 路径的文件,无需手动添加。

实现这段动画的大致思路就是,首先所有元素默认 opacity 为 0,并且有向下的位移,当元素移动到视口内即添加标签设置为可见,并恢复位置,当然也可以配合 filter blur 等实现模糊渐显的效果,于是写好如下 css

CSS
1/* 初始状态,元素处于下方且不可见,并带有模糊效果 */
2.scroll-item {
3  opacity: 0;
4  filter: blur(10px); /* 元素模糊 25px */
5  transform: translateY(20px); /* 元素初始位移 20px */
6  transition: transform 0.5s ease-out, opacity 0.5s ease-out, filter 0.5s ease-out; /* 为 filter 添加动画 */
7}
8
9/* 当元素进入视口时,透明度变为 1,模糊度变为 0,且上移回原位 */
10.scroll-in {
11  opacity: 1;
12  filter: blur(0); /* 模糊效果消失 */
13  transform: translateY(0); /* 元素回到原始位置 */
14  transition: transform 0.5s ease-out, opacity 0.5s ease-out, filter 0.5s ease-out; /* 确保 filter 也有动画 */
15}

有了思路之后就写好对应的指令 js
/plugins/scrollSpring.js

JS
1// 导出一个 Nuxt 插件
2export default defineNuxtPlugin((nuxtApp) => {
3    // 定义一个自定义指令 'scroll-spring'
4    nuxtApp.vueApp.directive('scroll-spring', {
5        // 当元素被挂载到 DOM 中时调用的钩子
6        mounted(el) {
7            // 设置 Intersection Observer 的选项
8            const options = {
9                root: null, // 使用浏览器视口作为根元素
10                rootMargin: '0px', // 根元素的边距
11                threshold: 0.1 // 交叉的阈值,当 10% 的目标元素在视口内时触发回调
12            };
13
14            // 观察者回调函数
15            const callback = (entries) => {
16                entries.forEach(entry => {
17                    // 如果目标元素与视口相交
18                    if (entry.isIntersecting) {
19                        // 添加动画类
20                        el.classList.add('scroll-in'); 
21                    } else {
22                        // 移出视口时移除动画类
23                        el.classList.remove('scroll-in'); 
24                    }
25                });
26            };
27
28            // 创建一个 Intersection Observer 实例,并传入回调函数和选项
29            const observer = new IntersectionObserver(callback, options);
30            // 开始观察当前元素
31            observer.observe(el);
32        },
33        // 当元素从 DOM 中卸载时调用的钩子
34        unmounted(el) {
35            // 创建一个空的 Intersection Observer 实例
36            const observer = new IntersectionObserver(() => {});
37            // 停止观察当前元素
38            observer.unobserve(el);
39        }
40    });
41});

这里我们利用 Intersection Observer ,当元素进入视口触发回调添加类名,移出视口则去掉类名

接下来我们只要在对应的元素引入就可以啦!
注意 scroll-itemv-scroll-spring 缺一不可,前者负责决定开始状态,后者是动画和结束效果

这里简单贴一个代码片段,也可以参考我主页对应的仓库~
/pages/index.vue

VUE
1<div class="slogan font-jb-mono scroll-item" v-scroll-spring>
2          <p>Coding,</p>
3          <p>build a better world</p>
4          <p>together!</p>
5        </div>
6        <span class="slogan-cn scroll-item" v-if="locale === 'zh'" v-scroll-spring>{{ t('slogan.cn') }}</span>
7        <br/>
8        <div class="button-container scroll-item" v-scroll-spring>
9          <UButton to="https://github.com/grtsinry43" target="_blank"
10                   icon="i-grommet-icons:github" style="vertical-align: -4px"
11                   class="btn-item github-link bg-blue-400 text-black dark:bg-blue-800 dark:text-white">
12            {{ t('buttons.github') }}
13          </UButton>
14          <UButton :label="t('buttons.learningLog')" color="gray" class="btn-item scroll-item" v-scroll-spring>
15            <template #trailing>
16              <UIcon name="i-heroicons-arrow-right-20-solid" class="w-5 h-5 btn-more-icon"/>
17            </template>
18          </UButton>
19          <UButton color="gray" class="btn-item scroll-item" disabled v-scroll-spring>
20            {{ t('buttons.resume') }}
21          </UButton>
22        </div>

最后效果

https://blogoss.grtsinry43.com/uploads/24/10/dfd9b723931112b166843ff4b28d68fa.gif/improved

相关推荐

用一个月的时间写一个自己的博客系统——Grtblog的技术介绍

终于,历时一个多月的开发 ~~bug~~ 和测试,这个目前问题很多很不成熟很难用的系统终于上线了.....

grtsinry43
12/14/2024
117
4
0

使用 BeautifulSoup 配合请求库实现简单的爬虫程序

事情起源于课内的课程实验作业...因为要求要用爬虫,~~不必说课内讲的一言难尽,更不必说就算讲了我也...

grtsinry43
1/7/2025
67
0
0

折腾记录|使用 Nuxt.js 重写个人主页,使用 SSR 优化 SEO ,实现一些期待已久的效果

在 22 年刚创建个人主页的时候,由于我的技术水平不够,只能用一些 wordpress type...

grtsinry43
9/19/2024
24
0
0

快速搭建专属域名邮箱服务,简单整合邮箱消息推送功能至 Spring Boot 应用程序

作为即时通信的重要方式,邮件在互联网互动中起到举足轻重的作用,而搭建邮件推送服务不仅可以做到实时的消...

grtsinry43
1/2/2025
70
1
0

学习分享|跨域解决、安卓开发探索、油猴脚本探索

最近学习的一些内容,包括跨域问题及其解决方案,安卓开发的简单探索,OpenAI的api做了个小插...

grtsinry43
6/10/2024
35
0
0
COMMENT 7273220598938079232

发表评论

登录之后评论体验更好哦 ~
支持 Markdown 语法 0 / 3000

在风雨飘摇之中

本站已运行了

一路走来,感谢陪伴与支持

愿我们不负热爱,继续前行

全站通知
更新通知