vue3 拆信封动画

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 打开 开发者工具直接拿就行了

相关推荐
xcLeigh39 分钟前
HTML5实现好看的博客网站、通用大作业网页模板源码
前端·课程设计·html5
mit6.82440 分钟前
[Qt] 输入控件 | Line | Text | Combo | Spin | Date | Dial | Slider
前端·qt·学习·ubuntu
狗狗显卡2 小时前
一些计算机零碎知识随写(25年1月)
前端
Burt2 小时前
【unibest】可以去掉hbx模版了,base模板一统天下
前端·微信小程序·uni-app
拖孩2 小时前
💥大家好,我是拖孩🎤
前端·javascript·后端
CHANG_THE_WORLD2 小时前
Linux 基础七 内存
linux·服务器·前端
Z3r4y3 小时前
【Web】极简&快速入门Vue 3
前端·javascript·vue.js·vue3
cxr8283 小时前
Windows 11 系统中npm-cache优化
前端·windows·npm
小小小小宇3 小时前
如何在 React 中实现 Vue 的插槽功能
前端
工业甲酰苯胺3 小时前
Java Web学生自习管理系统
java·开发语言·前端