Creating a Love Envelope Animation with Vite and Vue 3
This tutorial walks through building an interactive love‑envelope animation using Vite, Vue 3, CSS, and canvas, covering envelope rendering, opening animation, text fade‑in, confetti effects, and dialogue overlays with complete code examples.
The article introduces a Valentine‑themed love‑envelope animation built with Vite and Vue 3, providing links to the online demo and source repository.
Envelope Rendering
The envelope consists of three parts: the outer skin, a heart‑shaped wax seal, and the inner card. The HTML structure is:
<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>The accompanying SCSS defines colors and the envelope’s pseudo‑elements for the outer skin and the top shadow:
$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;right:0;bottom: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,.3) transparent transparent transparent;transition:.3s all;transform-origin:50% 0%;z-index:7;filter:blur(2px);border-radius:5px;}
}The top flap is styled similarly with its own pseudo‑element:
.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;}The heart seal is a rotated square with two circular pseudo‑elements to form the classic heart shape:
.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:0;}
&::after{content:'';display:block;width:15px;height:15px;border-radius:50%;position:absolute;background-color:$heart-color;right:0;top:-8px;}
}Envelope Opening
When the heart is clicked, a random message is loaded from content.json and displayed inside the card. The opening triggers a series of CSS animations defined on the .envelope.active state:
.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);}}Text Fade‑In
The card’s paragraph uses a filter animation to transition from blurred to clear text:
.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;padding:5% 6.8%;filter:contrast(20);
p{font-size:22px;color:$heart-color;font-family:fancy;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);}}Confetti Effect
The celebratory confetti is generated with the canvas‑confetti library. After installing via npm i -S canvas‑confetti , the script repeatedly fires two confetti bursts from opposite sides until a 10‑second timer expires:
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});
confetti({particleCount:2,angle:120,spread:55,origin:{x:1},colors});
if(Date.now()
Dialogue Overlay
A semi‑transparent overlay shows random rejection messages with accompanying glass‑break images and sound effects. The overlay markup and logic are:
<!-- Dialogue overlay -->
<div class="word" :class="{'active':wordShow}">{{word}}</div>
<!-- Glass images -->
<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>
The
addSorry
function pushes a random glass‑break image, plays the sound, and displays a random message after a short delay.
Conclusion
The tutorial demonstrates how to combine Vue 3, Vite, CSS animations, and canvas to create a playful love‑envelope effect that can be extended for various interactive scenarios.Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.