最简Vue3实战:做我女朋友好不好
前言碎语
在一种莫名力量的推动下,每年会有好多个情人节,但是七夕,应该还是我们最传统的一个。在鹊桥相会的重要日子里,你需要来一个Vue3的实战么?
你说啥,想要和女朋友一起过七夕,但是不知道她愿不愿意?那就花费10分钟时间来搞定这个最简Vue3实战吧,还可以从容地发给她:做我女朋友好不好?
完整项目体验地址:train.lovetime.top/qixi/
项目奠基
因为是最简实战,所以我们这个项目比较简单,用不上router
、pinia
这些,但是我们也不能太草率,用pnpm
来玩一玩吧。
pnpm create vite ./qixi-girlfriend
按照提示操作即可,当然我们要记得选择Vue
模板,也可以直接指定模板的方式来玩:
pnpm create vite ./qixi-girlfriend --template vue
进入项目,安装基础依赖,这都是老一套流程,不再baba赘述:
cd qixi-girlfriend
pnpm install
pnpm run dev
项目进行时
捋一捋流程
(1) 初始状态下呢,我们要展示“做我女朋友好不好”的主题,表明一下自己的心声。
(2) 展示同意Yes
和拒绝No
的按钮。
(3) 点击Yes
按钮同意时,说明女朋友同意啦,展示比心
状态,并说出你的诺言。
(4) 点击No
按钮拒绝时,赶紧拯救一下自己。一条条插入你表白的理由,如果理由展示完了,展示可怜
的状态。
静态资源的加载
由于vite
不再支持webpack
的require
加载模式,我们简单封装一下new URL()
方法来引入使用的静态资源。
const getResourceUrl = (name, ext = "png") => {
return new URL(`./assets/${name}.${ext}`, import.meta.url).href;
};
getResourceUrl()方法支持传入文件名和后缀格式。
核心实现
由于我们的功能比较简单,直接在App.vue
里面写起来。
变量赋值
我们首先通过ref
和getResourceUrl
来定义赋值一下需要使用的变量和资源。
<script setup>
import { ref } from "vue";
const refuseNum = ref(0);
const isDecisionShow = ref(true);
const isAgreeShow = ref(false);
const title = ref("做我女朋友好不好");
const initText = ref(
"承蒙你的出现,够我喜欢好多年,我希望,以后你能用我的名字拒绝所有人"
);
const benefitText = ref([
"你是我拔掉氧气罐都想吻的人",
"你是我跑完8000米还想拥抱的人",
"你是我自罚三杯都不肯开口的秘密",
"你是我赴汤蹈火都不肯放下的执着",
"你是我电量只剩1%也想回信息的人",
"你是我穷极一生不想醒来的梦",
]);
const resultText =
"遇见你是我所有美好故事的开始,所以,请别放开我的手,也别缺席我的将来,因为一辈子和你在一起才叫将来";
const exhibitionText = ref([initText]);
const winkImg = getResourceUrl("wink", "gif");
const bgImg = getResourceUrl("bg", "jpg");
const kelianImg = getResourceUrl("emoji_kelian", "jpg");
const bixinImg = getResourceUrl("emoji_bixin", "jpg");
</script>
在template
模板中使用这些变量
<template>
<div class="container">
<img class="bg" :src="bgImg" />
<div class="lover">
<div class="express">
<h1>{{ title }}<span>💕</span></h1>
<div class="wink">
<img :src="winkImg" />
</div>
<p v-for="(text, index) in exhibitionText" :key="index">{{ text }}<span>💕</span></p>
</div>
<div class="pray" v-show="!isDecisionShow" @click="onPray">
<img :src="kelianImg" />
<p>请告诉我Yes!</p>
<span class="pray-close">×</span>
</div>
<div class="decision" v-show="isDecisionShow">
<div class="decision-btn refuse" @click="onRefuse">No<span>💔</span></div>
<div class="decision-btn" @click="onAgree">Yes<span>❤️</span></div>
</div>
<div class="agree-wrapper" v-show="isAgreeShow">
<div class="agree">
<img :src="bixinImg" />
<p>太好了,O(∩_∩)O哈哈~</p>
<p>{{ agreeText }}<span class="agree-cursor" style="color: #f44336">❤</span></p>
</div>
</div>
</div>
</div>
</template>
定义方法
当点击No
按钮拒绝时,把下一条表白话语插入到展示列表中,并对拒绝次数进行累加。若表白话语展示完了,展示可怜
状态并隐藏决策按钮;点击可怜
状态时立马恢复。
const onRefuse = () => {
console.log("onRefuse", refuseNum.value);
if (refuseNum.value < benefitText.value.length) {
exhibitionText.value.push(benefitText.value[refuseNum.value]);
refuseNum.value++;
} else {
isDecisionShow.value = false;
}
};
const onPray = () => {
isDecisionShow.value = true;
};
当点击Yes
按钮同意时,展示比心
状态,并且实现一个简陋版的打字效果,展示你的诺言。
const onAgree = () => {
isAgreeShow.value = true;
onTyped();
};
const onTyped = () => {
let index = 0;
const typedTime = setInterval(() => {
agreeText.value = resultText.substring(0, index++);
}, 150);
if (index >= resultText.length - 1) {
clearInterval(typedTime);
}
};
好啦,到这里,我们核心的功能已经实现啦。
Emm,但是总感觉有那么点点不够唯美,我们来加上花瓣飘飘的动效吧。
动效加持
说起来动效,想起前一段和PixiJS
一起食用的GSAP
,详见历史文章。那我们先安装依赖,当然为了让每一片花瓣都唯一,我们再安装一下nanoid
帮助生成唯一ID吧。
pnpm install gsap
pnpm install nanoid
# 为了使用sass的习惯,补充安装一下sass到devDependencies
pnpm install sass -D
创建花瓣
首先,我们来准备一组花瓣素材,不同样式的花瓣让我们在素材方面省了不少事(当然,其实我们也是可以直接用css来画的)~
然后,在创建花瓣的时候,根据可视屏幕的宽高来随机一下花瓣初始位置,以及配合GSAP
动效的目标位置和动画时长duration
。
最后,把创建好的花瓣元素插入到花瓣列表,并且在花瓣动效结束后移除它。
当然,我们还是要通过定时器setInterval
在onMounted
周期挂载并持续创建花瓣,在onUnmounted
周期移除定时器。
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
// 为了使ID符合规则且够简单,我们自定义一下ID的生成规则
import { customAlphabet } from "nanoid";
const nanoid = customAlphabet("abcdefghijklmn", 6);
// 花瓣素材
const petalImgs = [
"icon_petal_1",
"icon_petal_2",
"icon_petal_3",
"icon_petal_4",
"icon_petal_5",
"icon_petal_6",
"icon_petal_7",
"icon_petal_8",
];
const petalList = ref([]);
const visualWidth = window.innerWidth;
const visualHeight = window.innerHeight;
// 创建花瓣元素
const createPetalBox = () => {
// 配合getResourceUrl()方法随机获取素材*1
const currentPetal = getResourceUrl(petalImgs[Math.floor(Math.random() * 8)]);
// 初始的随机位置
const petalLeft = Math.random() * visualWidth;
// 初始透明度
const randomOpacity = Math.random();
const petalOpacity =
randomOpacity < 0.5 ? randomOpacity + 0.5 : randomOpacity;
// 动效结束的随机位置
const petalEndLeft = petalLeft - 100 + Math.random() * 500;
const petalEndTop = visualHeight + 40;
// 动效时长
const duration = Math.floor(
(visualHeight * 10 + Math.random() * 5000) / 1000
);
const currentStyle = {
left: petalLeft,
opacity: petalOpacity,
};
const petal = {
id: nanoid(),
url: currentPetal,
style: currentStyle,
end: {
duration,
left: petalEndLeft,
top: petalEndTop,
},
};
petalList.value.push(petal);
};
// 动效结束后移除当前花瓣元素
const removeHandler = (id) => {
petalList.value.splice(
petalList.value.findIndex((petal) => petal.id === id),
1
);
};
// 定义花瓣定时器
const petalTimer = ref(null);
const petalHandler = () => {
petalTimer.value = setInterval(createPetalBox, 500);
};
onMounted(() => {
petalHandler();
});
onUnmounted(() => {
clearInterval(petalTimer);
});
</script>
创建自定义组件
接下来我们创建一个自定义组件WePetal
,可以在组件挂载完成后,通过props传递的元素ID绑定GSAP
动效,并在动态结束后通知父组件。
<template>
<img :id="petal.id" class="petal" :src="petal.url" :style="petal.style" />
</template>
<script setup>
import { onMounted } from "vue";
import gsap from "gsap";
const props = defineProps({
petal: {
type: Object,
default() {
return {};
},
},
});
const emit = defineEmits(['remove'])
onMounted(() => {
const { id, end } = props.petal;
// 通过唯一ID来绑定动效
gsap.to(`#${id}`, {
...end,
onComplete: () => {
emit('remove', id)
}
});
});
</script>
<style lang="scss" scoped>
.petal {
width: 24px;
height: 24px;
position: absolute;
top: -40px;
left: 0;
opacity: 1;
z-index: 99;
}
</style>
调用子组件
现在,我们就可以在App.vue
中引入子组件并且使用啦。
<template>
<!--...-->
<div class="petal-box">
<WePetal
v-for="petal in petalList"
:key="petal.id"
:petal="petal"
@remove="removeHandler"
/>
</div>
</template>
写在最后
其实,还有好多细节需要优化的,比如素材的选择、比如表白语录、比如为了花瓣不遮挡按钮事件直接放在了下层等等,主要还是七夕马上就要到了,担心影响大家表白~
祝大家七夕快乐~
转载自:https://juejin.cn/post/7127583327841681415