snows_l's BLOGhttp://snows-l.site/
一、效果如下
截图工具截图效果不是很好, 可以查看线上效果
信封 | snows_l's BLOGhttp://snows-l.site/about/like/envelope
二、源码如下
html
<!--
* @Description: ------------ fileDescription -----------
* @Author: snows_l snows_l@163.com
* @Date: 2025-01-02 19:05:25
* @LastEditors: snows_l snows_l@163.com
* @LastEditTime: 2025-01-03 15:19:59
* @FilePath: \BLOG\src\views\love\envelope\index.vue
-->
<template>
<div class="envelope-warp">
<div class="envelope-inner" :style="{ scale: isMobi ? 0.6 : 1 }" :class="{ 'is-flip': state.isFlip, 'no-flip': !state.isFlip }">
<div class="back">
<div class="flip-warp">
<div class="latter-warp" :class="{ 'is-chou': state.isOpen }">
<div class="latter-top" :style="{ transform: state.isOpen ? 'rotateX(180deg)' : 'rotateX(0deg)' }">
<div class="latter-top-front"></div>
<div class="latter-top-back">
<div
:style="{ opacity: state.isOpen ? 1 : 0, '--c': state.isOpen ? '0.5s' : '0.3s' }"
class="close pointer iconfont icon-cc-close-crude"
@click="() => (state.isOpen = false)"></div>
</div>
</div>
<div class="latter-inner" :class="{ 'is-transform': state.isOpen }" :style="{ '--b': state.isOpen ? '0.7s' : '0.7s' }">
<div class="latter-bottom">
<div class="header">Dear Lover:</div>
<div class="latter-content">
<div class="latter-content-inner">
<div class="latter-content-item" v-for="(item, index) in state.contents" :key="index">{{ item }}</div>
</div>
</div>
<div class="footer">------ Your Lover</div>
</div>
</div>
</div>
<div class="bottom">
<div class="flip-btn pointer" @click="handleFlip(true)">Flip</div>
</div>
<div class="top" :class="[state.isOpen ? 'is-open' : 'no-open']" :style="{ '--a': state.isOpen ? '0s' : '1.2s', 'z-index': state.isOpen ? '98' : '100' }">
<div class="top-back"></div>
<div class="top-front">
<div class="yinzhang pointer" :style="{ 'background-position': state.isFirstOpen ? '0 100%' : '0 200%' }" @click="handleOpen"></div>
</div>
</div>
</div>
</div>
<div class="front">
<h1>Dear Lover</h1>
<div class="flip-btn pointer" @click="handleFlip(false)">Flip</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import useResize from '@/hooks/useResize';
const { isMobi } = useResize();
const state = reactive({
isFlip: false,
isOpen: false,
isFirstOpen: false,
contents: []
});
const content = `💖 见字如晤,展信舒颜。这封信承载着我对你深深的思念。
🌹 喜欢你,或许是这个世界上最无法解释的事情。不是权衡利弊,不是见色起意,而是突然间有了你,让我牵肠挂肚,无法割舍。
🌈 你是我安稳岁月里的意外,是我平淡生活里的星辰大海。我不曾搜索爱的定义,因为当我开车等红灯时,转头看到你坐在副驾驶,那就是爱;当我清晨醒来,希望第一眼看到的是你的睡颜,那也是爱。
🌅 无论是清晨的日出,还是正午的悠闲,或是漫天星辰的夜晚,我都想与你共度。我的爱,无需理由,只因是你。
💍 遇见你,是故事的开始;而你,是我余生的欢喜。做过最勇敢的事,就是在最真实的时候选择送你离开,因为你值得更好的。
🌸 你是一树一树的花开,是温暖的希望,是人间四月天。我试图诅咒你,只因你只能待在我身边,只爱我一个人。但我知道,人生短暂,我想娶自己爱的人。
🌻 你是我生命中不可或缺的一部分,是我一生的希望。我不想放弃你,不想放弃爱的感觉。我不想再让你孤单,不想再让你孤独。
🌼 你是我生命中最美的风景,是我生命中最值得珍惜的记忆。
🌲 你是我生命中最美的记忆,是我生命中最珍贵的回忆。
🌴 你是我生命中最美的风景,是我生命中最值得珍惜的记忆
🌱 你是我生命中最美的记忆,是我生命中最珍贵的回忆
👨❤️👩👧👦 我曾以为我会孤独终老,直到遇见你。我不敢承诺一生不惹你生气,但在我能想象的未来里,只想对你一个人好。纵使生活不易,我还是想把你放在未来里,一生欢喜,纯净如初。
`;
state.contents = content.split('\n');
// 信封翻转
const handleFlip = (isBackFlip: boolean = false) => {
if (isBackFlip && state.isOpen) {
state.isOpen = false;
setTimeout(() => {
state.isFlip = !state.isFlip;
}, 1.4 * 1000);
} else {
state.isFlip = !state.isFlip;
}
};
// 打开/关闭信封
const handleOpen = () => {
state.isOpen = !state.isOpen;
if (!state.isFirstOpen) state.isFirstOpen = true;
};
</script>
<style lang="scss" scoped>
.envelope-warp {
width: 100vw;
height: 100vh;
position: relative;
display: flex;
justify-content: center;
align-items: center;
.envelope-inner {
margin-top: 100px;
position: relative;
width: 600px;
height: 300px;
.front {
backface-visibility: hidden;
position: absolute;
top: 0;
left: 0;
width: 600px;
height: 300px;
font-size: 1.25em;
background: url('@/assets/images/common/letterStamp.png') no-repeat 20px 20px, beige url('@/assets/images/common/letterBg.png');
border: 1px solid #eae1d5;
box-shadow: inset 0 0 10px 1px hsla(0, 0%, 100%, 0.6), 0 2px 3px -2px rgba(0, 0, 0, 0.6);
padding: 20px;
color: #837362;
text-shadow: 0 1px 0 #fff, 0 1px 0 #fff;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.flip-btn {
position: absolute;
bottom: 20px;
right: 20px;
font-size: 18px;
font-weight: 600;
}
}
.back {
transform: rotateY(-180deg);
position: absolute;
top: 0;
left: 0;
width: 600px;
height: 300px;
color: #837362;
background-color: #837362;
text-shadow: 0 1px 0 #fff, 0 1px 0 #fff;
.flip-warp {
width: 600px;
height: 300px;
position: relative;
perspective: 100;
// 信封 上半部分
.top {
transform-style: preserve-3d;
position: absolute;
left: 0;
top: 0;
z-index: 101;
width: 100%;
height: 150px;
border-bottom-right-radius: 30px;
border-bottom-left-radius: 30px;
box-shadow: inset 0 0 10px 1px hsla(0, 0%, 100%, 0.6), 0 2px 3px -2px rgba(0, 0, 0, 0.6);
transition: transform 0.7s ease var(--a), z-index 0.7s ease var(--a);
transform-origin: top center;
.top-front {
backface-visibility: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 150px;
background: beige url('@/assets/images/common/letterBg.png');
border-bottom-right-radius: 30px;
border-bottom-left-radius: 30px;
border-bottom: 1px solid #eae1d5;
.yinzhang {
width: 150px;
height: 150px;
background-image: url('@/assets/images/common/letterStitch.png');
background-size: 100% 200%;
position: absolute;
bottom: -71px;
left: 50%;
margin-left: -75px;
}
}
.top-back {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 150px;
background-color: #837362;
border: 35px solid hsla(0, 0%, 100%, 0.1);
border-top: 2px solid #8f8579;
box-shadow: inset 0 10px 30px 10px rgba(0, 0, 0, 0.1);
border-bottom-right-radius: 30px;
border-bottom-left-radius: 30px;
}
}
// 信封 下半部分
.bottom {
position: absolute;
left: 0;
bottom: 0;
z-index: 100;
width: 100%;
height: 200px;
background: beige url('@/assets/images/common/letterBg.png');
border: 1px solid #eae1d5;
box-shadow: inset 0 0 10px 1px hsla(0, 0%, 100%, 0.6), 0 2px 3px -2px rgba(0, 0, 0, 0.6);
.flip-btn {
position: absolute;
bottom: 20px;
right: 20px;
font-size: 18px;
font-weight: 600;
}
}
// 信纸
.latter-warp {
background-color: #fff;
margin: 5%;
width: 90%;
position: absolute;
left: 0;
top: -8%;
z-index: 99;
transition: all 0.7s ease 0.7s;
.latter-top {
height: 40px;
background-color: #fff;
transform-style: preserve-3d;
transition: transform 0.7s ease 0.3s;
transform-origin: top center;
// backface-visibility: hidden;
.latter-top-back {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 10px 20px;
font-size: 16px;
font-weight: 600;
// background-color: #f2f2f2;
border-bottom: 1px solid #f2f2f2;
border-top: 1px solid #f2f2f2;
.close {
transition: all 0.3s ease var(--c);
}
}
.latter-top-front {
position: absolute;
top: 0px;
left: 0;
height: 100%;
width: 100%;
}
}
.latter-inner {
margin-top: -40px;
backface-visibility: hidden;
width: 100%;
height: 100%;
transform: rotateX(-180deg);
transform-style: preserve-3d;
transition: all 0.7s ease var(--b);
.latter-bottom {
background-color: #fff;
.latter-content {
padding: 0 20px;
.latter-content-inner {
height: 197px;
overflow-y: auto;
.latter-content-item {
line-height: 24px;
font-size: 16px;
text-indent: 32px;
min-height: 24px;
}
}
}
.header {
height: 27px;
line-height: 27px;
padding: 0 20px;
text-align: left;
margin: 8px 0;
font-size: 20px;
font-weight: 600;
}
.footer {
padding: 18px 0;
line-height: 27px;
padding: 0 20px;
text-align: right;
margin: 12px 0;
font-size: 20px;
font-weight: 600;
}
}
}
.is-transform {
transform: rotateX(0deg);
}
}
.is-chou {
transform: translateY(-68%);
}
}
}
}
.is-flip {
transform: rotateY(180deg);
transform-style: preserve-3d;
transition: transform 0.7s 0s;
transform-origin: center center;
}
.no-flip {
transform: rotateY(360deg);
transform-style: preserve-3d;
transition: transform 0.7s 0s;
transform-origin: center center;
}
.is-open {
transform: rotateX(-180deg);
}
.no-open {
transform: rotateX(0deg);
}
}
@keyframes flip {
}
</style>
如果想要资源的直接去我的 snows_l's BLOG 打开 开发者工具直接拿就行了