我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!
介绍
请不要叫我单身狗,我的代号是战狼。七夕将至,本期就献上一个七夕表白信封,打开信封就有你对她想说的话,毕竟单身直男一般当面也说不出口,咱今儿就用H5动画的这种形式表达出来,标题说发家致富,这是怎么回事呢?当然打开你就知道了。
在线演示:jsmask.gitee.io/love-envelo…
正文
接下来就开启正文了,本案例将使用vite与vue3进行开发制作,以下将从几个主要核心点来解析这个效果,其中一些比较基础的地方或者繁琐的业务将会略过。
绘制信封
信封的绘制主要分为三部分,分别为信封的皮面(头部开口),爱心封蜡,信纸。
<div class="envelope" :class="{'active':open}">
<div class="top"></div>
<div class="heart" @click="handleOpen"></div>
<div class="card"><p :style="{'font-size':fontSize+'px'}">{{ content }}</p></div>
<canvas ref="canvas"></canvas>
</div>
这里提前说明加上canvas是后面为了做纸片撒花动画做的容器所以先写在里面了。
$heart-color:rgb(248, 82, 82);
$envelope-color:rgb(252, 240, 175);
$in-color:rgb(252, 252, 198);
$msg-color:rgb(192, 82, 82);
.envelope{
width: 280px;
height: 160px;
background: $in-color;
position: relative;
box-shadow: 2px 2px 10px rgba(0,0,0,.2);
border-radius:5px;
canvas{
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 100;
}
&::after {
content: '';
display: block;
position: absolute;
border-width: 80px 140px;
top: 0;
border-style: solid;
border-color: transparent $envelope-color $envelope-color $envelope-color;
transition: .3s all;
transform: rotateX(0deg);
transform-origin: 50% 0%;
z-index: 6;
border-radius: 5px;
}
&::before {
content: '';
display: block;
position: absolute;
border-width: 80px 140px;
top: 0;
border-style: solid;
border-color: rgba(0, 0, 0, 0.3) transparent transparent transparent;
transition: .3s all;
transform-origin: 50% 0%;
z-index: 7;
filter: blur(2px);
border-radius: 5px;
}
}
这里两个伪元素,其中 after
是用来给信封的加个外皮颜色,比因为信封内部和外皮会出现色差不能用同一色这样才显得有层次感,另外 before
的就是要给信封的顶部开口部分增加一层阴影效果,也是为了更立体形象把信封的层次表现出来。
.top{
position: absolute;
border-width: 80px 140px;
top: 0;
left: 0;
border-style: solid;
border-color: $envelope-color transparent transparent transparent;
transform: rotateX(0deg);
transform-origin: 50% 0%;
z-index: 8;
border-radius: 5px;
}
这里我们在做信封还有把 transform-origin
设置成 50% 0%
,主要是考虑到后面的信封打开会做大小和角度的过渡动画,其起点肯定是信封的边框为做基点的。
至于最后,用css怎么绘制一款红心,这个就非常的简单了。其实就是一个矩形旋转一下尖部朝下,然后用伪元素做两个小圆分别在两侧做个圆就大功告成了~
.heart{
position: absolute;
width: 15px;
height: 15px;
transform: rotate(45deg);
z-index: 520;
background-color:$heart-color;
left: 50%;
margin-left: -2px;
top:75px;
transition: .3s all;
transform-origin: 50% 0%;
box-shadow: -3px 3px 16px rgba(0, 0, 0, .6);;
cursor: pointer;
&::before{
content: '';
display: block;
width: 15px;
height: 15px;
border-radius: 50%;
position: absolute;
background-color: $heart-color;
left: -8px;
top: 0px;
}
&::after{
content: '';
display: block;
width: 15px;
height: 15px;
border-radius: 50%;
position: absolute;
background-color: $heart-color;
right: 0px;
top: -8px;
}
}
信封开启
要想表白不是咱们先要有要说的话不是,这里我用了几段话术,随机出一段来填充进去。
import { list } from "./content.json"
import { watch } from 'vue'
watch(open, v => {
if(!v) return;
end.value = false;
let msg = list[~~(Math.random()*list.length)];
fontSize.value = msg.fontSize;
setTimeout(()=>bomm(),1000)
setTimeout(()=>contentLog(msg.text),2222)
});
这里监听,当我们打开后,随机抽出一段话来,然后展示这个文字。
而信封的主容器接收到开启后,也是会激活下面的一系列的动画指令,比如说爱心蜂蜡晃动脱落,信件弹出展示等。
.envelope{
&.active{
transition: .8s 1s transform;
transform: translateY(50px);
&::before{
animation: hide .2s 1s ease-out;
animation-fill-mode:forwards;
}
&::after{
animation: afterUp .2s 1s ease-out;
animation-fill-mode:forwards;
}
.top{
transition: .5s all;
transition-delay: 1s;
transform: rotateX(180deg) translateY(-2px) scaleY(1.5);
}
.card{
animation: show .8s 1.5s ease-out;
animation-fill-mode:forwards;
z-index: 9;
p{
animation: showContent 2.5s 1s ease-out forwards;
}
}
.heart{
animation: heartBeat 1s;
animation-fill-mode: forwards;
}
}
}
@keyframes show{
0%{
transform: scaleY(.6) translateY(20px);
opacity: .7;
}
100%{
transform: scaleY(1) translateY(-80px);
opacity: 1;
}
}
@keyframes hide{
0%{
opacity: 1
}
100%{
opacity: 0;
}
}
@keyframes afterUp{
0%{
z-index:8;
}
100%{
z-index:10;
}
}
@keyframes heartBeat {
0% {
transform: rotate(45deg) scale(1);
}
14% {
transform: rotate(45deg) scale(1.3);
}
28% {
transform: rotate(45deg) scale(1);
}
42% {
transform: rotate(45deg) scale(1.3);
}
70% {
transform: rotate(45deg) scale(1);
}
100%{
transform: rotate(0deg) translateY(5px);
}
}
文字渐入
重点来了,那么这些文字是怎么过渡的,从模糊到慢慢清晰的效果呢?
.card {
width: 240px;
height: 180px;
background-color: white;
position: absolute;
z-index: 5;
left: 50%;
margin-left: -120px;
bottom: 20px;
transition: .2s .1s all;
box-shadow: 1px 1px 15px rgba(0,0,0,.2);
opacity: 0;
box-sizing: border-box;
padding: 5% 6.8%;
filter: contrast(20);
p{
font-size: 22px;
color: $heart-color;
font-family: fantasy;
line-height:32px;
-webkit-text-stroke:1px $heart-color;
text-shadow: -1px 0 1px #f5e6c2, 0 1px 2px #fff0c9;
}
}
@keyframes showContent{
0%{
filter: blur(12px);
}
100%{
filter: blur(0px);
}
}
答案在于其 filter
属性的 contrast
与 blur
之间的相互作用,信件容器赋予 filter:contrast(20)
, 再在 animation
动画上把模糊逐渐降低,就是这么简单就实现了这个效果,但目前发现个问题还没有什么好的解决方案,就是苹果系统在做这个动画的时候会和其他系统的过渡效果会稍有些出入。
撒花效果
可以看到演示上有非常多的撒花的效果,你会问怎么实现的呢?这里我们就用到一个专门做这种效果的插件了—— canvas-confetti。
我们先来安装一下:
npm i -S canvas-confetti
使用起来也非常的简单,可以查阅 canvas-confetti 的官方文档,然后组合出炒鸡绚丽的动画出来。
import confetti from "canvas-confetti"
async function handleSucces(params) {
if(fire.value || btnEnd.value) return;
// ...
const endTime = Date.now() + (10 * 1000);
const colors = ['#bb0000', '#ffffff'];
(function frame() {
confetti({
particleCount: 2,
angle: 60,
spread: 55,
origin: { x: 0 },
colors: colors
});
confetti({
particleCount: 2,
angle: 120,
spread: 55,
origin: { x: 1 },
colors: colors
});
if (Date.now() < endTime) {
requestAnimationFrame(frame);
fire.value = true
}else{
fire.value = false
}
}());
await talk("我没听错吧?!",0)
// ...
end.value = true
}
对话展示
我们就拿点击拒绝的时候来讲解,其实就是写一个div,通过控制一个黑色半透明弹层显隐,另外玻璃破碎的音效和图片表示咱此时不能接受现实的心情。
<!-- 对话弹层 -->
<div class="word" :class="{'active':wordShow}">{{word}}</div>
<!-- 玻璃图片 -->
<img v-for="(item,index) in sorryList"
:style="{'margin-left':item.x+'px','margin-top':item.y+'px','width':item.w +'px'}"
:key="item"
src="../../assets/sorry.png"
class="sorry" />
<audio ref="sorry" preload="auto">
<source src="../../assets/sorry.mp3" type="audio/mpeg">
</audio>
function addSorry(){
if(fire.value || btnEnd.value) return;
// 隐藏之前的对话框
wordShow.value = false;
// 延迟随机给一个回应
setTimeout(()=>{
word.value = [
"听,这是心碎的声音~",
"乖~别闹~~",
"?拒收好人卡?!",
"心碎了鸭~~",
"555...心碎+1",
"emmm...什么东西碎了?"
][~~(Math.random()*6)]
wordShow.value = true;
})
// 随机追加一个玻璃破碎的图片
sorryList.value.push({
x:300*Math.random()-300,
y:400*Math.random()-500,
w:200 + Math.random()*150
})
// 播放破碎的声音
sorry.value.currentTime = 0;
sorry.value.play()
}
// 执行显示对话框
function talk(msg,dur = 0) {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
wordShow.value = false;
},dur)
setTimeout(()=>{
word.value = msg
wordShow.value = true;
resolve()
},dur+200)
})
}
这里的对话方法,封装的比较粗糙,不要介意,但好在能用啊,这里就是显示一下黑色半透明弹框的话说安排好时间周期,为了有点过渡还额外加了200ms延时罢了。
结语
刚刚非常轻松的实现了一个信封特效,其实我们可以借助它表达出更多的内容来,这就有待大家自己的想法了。
话说都看到这里了不留个赞再走么,记住猥琐发育别浪,三十年舔狗经验值得保证。