抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

最近一直在学习 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-itemv-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

评论

在这里留下你的评论吧~