欢迎您访问新疆栾骏商贸有限公司,公司主营电子五金轴承产品批发业务!
全国咨询热线: 400-8878-609

新闻资讯

常见问题

利用Vue实现卡牌翻转的特效

作者:用户投稿2026-01-11 06:47:47
目录
  • 前言
  • 实现
    • 鼠标移入选中效果
    • 卡片翻转效果
  • 完整代码
    • 结语

      前言

      今天是正月初九,也是活动的倒数第二天,复工都三天了,而我三篇春节文章还没写完,实在是太混了!这次带来的是一个春节抽福卡页面,采用卡牌翻转的形式。

      实现

      以下所有的实现都是基于Vue2 来编写的。 (别骂了,我这就去学 Vue3

      鼠标移入选中效果

      从上面的效果图可以看到,当鼠标移动到某张福卡的时候,这张福卡就会放大,其余未被选中的卡片就会缩小且变得模糊。这样的视觉效果是通过 CSS 和 JS 一起控制的。

      其实只用 css 也可以达到类似的效果,但在细节上做的不是很好,只要我移动到wrap区域中,就会变得模糊,这个方案忽略了卡片与卡片之间的间隙,移入效果并没有精准地落到某个具体的卡片上,效果就像我下面展示的这样。如果有更好的只用CSS的方案可以底下评论区交流一下!

      // 没有被hover的卡片变模糊
      .wrap:hover .card{
          filter: blur(25px);
          transform: scale(.6); 
      }
      
      // hover卡片放大
      .card:hover {
          transform: scale(1.3);
          filter: none;
      }

      因此我们需要 JS 来一起控制。

      先来简单看一下 html 结构,这里我把一些不需要的代码先去掉了。

      <div class="wrap">
          <div class="card cover" v-for="item in cards" :key="item.id"
                          :class="{
                              'blur': item.id != hoverId && beginHover,
                          }" 
                          @mouseenter="hoverItem(item)" 
                          @mouseout="reset()">
          </div>
      </div>

      在上面的代码中,@mouseenter@mouseout 表示鼠标移入移出事件,但鼠标移入该 div 的时候就会触发事件,且只会触发一次。

      数据如下所示,其中 cards 存储卡片数据的数组,hoverId 表示当前鼠标选中的卡片的id,beginHover 表示鼠标是否开始接触到卡片。

      data(){
          return {
              cards: [
                  {id:0,content:'开工大吉',isTurned:false},
                  {id:1,content:'开工大吉',isTurned:false},
                  {id:2,content:'开工大吉',isTurned:false},
                  {id:3,content:'开工大吉',isTurned:false},
                  {id:4,content:'开工大吉',isTurned:false}
              ],
              hoverId: -1,
              beginHover: false,
          }
      },

      再来看看两个函数。其中 hoverItem 函数控制鼠标移入选中的卡片。我这里的设置是,若游戏结束了,则不再有任何的效果,因此需要事先判断下游戏是否结束。若没有结束,修改当前选中的卡片id与beginHover,表示可以开始变模糊了。

      hoverItem(item){
          if(!this.gameOver){
              this.hoverId = item.id
              this.beginHover = true
          }
      },
      reset(){
          this.beginHover = false
      },
      // 没有被hover的卡片变模糊
      .blur {
          filter: blur(25px);
          transform: scale(.6); 
      }
      
      // hover卡片放大
      .card:hover {
          transform: scale(1.3);
          filter: none;
      }

      概括一下就是,当鼠标第一次移入卡片中,这张卡片就会放大且不会添加滤镜,而其余未接触到的卡片都会添加 blur 类,缩放并变得模糊。

      卡片翻转效果

      PS:本文中的正面指的是封面,即有一个福字的。

      卡片翻转需要两个 div 来控制,分别是正面和背面。

      当点击的时候,通过修改 transform 属性来控制卡片的旋转,即设置 perspectiverotateY 来完成。旋转过来之后,若没有背面,我们可以看到“正面的背面”,他是没有内容的,因此需要设置我们自己背面的背景色来覆盖掉,并写入文本。这里需要注意的是,我们的背面也需要再绕Y轴旋转一次,不然看来的内容仍是相反的。

      那么如何让翻转效果看起来更协调呢,这里我的想法是,控制背面的背景色与封面保持一致,通过设置 setTimeout 函数让背面的内容晚点出来,以造成是我翻转卡片到某个角度后才能看见背面的文字。至于延时多久呢,这就慢慢调了哈哈。

      .back {
          position: absolute;
          width: 100%;
          height: 100%;
          left: 0;
          background: rgb(168, 10, 3);
          transform: rotateY(180deg);    // 还是需要旋转
          font-size: 1rem;
          color: #f1c40f;
          border-radius: 10px;
          display: flex;
          justify-content: center;
          align-items: center;
      }   

      翻转动画我设置的1s,延时用了400ms。

      setTimeout(() => {
          this.back = true
      }, 400);

      完整代码

      <template>
          <div class="the-card">
              <div class="wrap">
                  <div class="card cover" v-for="item in cards" :key="item.id"
                                  :class="{
                                      'cardHover': !gameOver,
                                      'blur': item.id != hoverId && beginHover,
                                      'turned': item.isTurned,
                                      'gameOver': !item.isTurned && gameOver,
                                  }" 
                                  @mouseenter="hoverItem(item)" 
                                  @mouseout="reset()"
                                  @click="turnItem(item)">
                      <div :class="{'back':back}" v-if="back">
                          <span>{{item.content}}</span>
                      </div>
                  </div>
              </div>
          </div>
      </template>
      
      <script>
      export default {
          data(){
              return {
                  cards: [
                      {id:0,content:'开工大吉',isTurned:false},
                      {id:1,content:'开工大吉',isTurned:false},
                      {id:2,content:'开工大吉',isTurned:false},
                      {id:3,content:'开工大吉',isTurned:false},
                      {id:4,content:'开工大吉',isTurned:false}
                  ],
                  hoverId: -1,
                  beginHover: false,
                  gameOver: false,
                  back: false
              }
          },
          methods: {
              hoverItem(item){
                  if(!this.gameOver){
                      this.hoverId = item.id
                      this.beginHover = true
                  }
              },
              reset(){
                  this.beginHover = false
              },
              turnItem(item){
                  if(this.gameOver) return
                  this.cards.map(card => {
                      card.id === item.id ? card.isTurned = true : "";
                  })
      
                  setTimeout(() => {
                      this.beginHover = false
                      this.gameOver = true
                  }, 300);
      
      
                  setTimeout(() => {
                      this.back = true
                  }, 400);
              }
          },
          created(){
              let randomNum = Math.floor(Math.random() * 5)
              this.cards[randomNum].content = '恭喜您获得【冰墩墩】'
          }
      }
      </script>
      
      <style lang="less" scoped>
          .the-card {
              height: 100vh;
              background-color: #bdc3c7;
              display: flex;
              justify-content: center;
              align-items: center;
              overflow: hidden;
      
              .wrap {
                  width: 100%;
                  display: flex;
                  justify-content: center;
                  align-items: center;
                  
                  .card {
                      width: 180px;
                      height: 300px;
                      margin: 0 1rem;
                      transition: 1s;
                      cursor: pointer;
                      border-radius: 10px;
                      position: relative;
                    
                  }
      
                  // 福卡封面
                  .cover {
                      background: url(../assets/fu.jpg) center center no-repeat;
                      background-size: 100% 100%; // 用contain会出现图片大小变化div外层不变
                  }  
      
                  // 没有被hover的卡片变模糊
                  .blur {
                      filter: blur(25px);
                      transform: scale(.6); 
                  }
      
                  // 卡片点击翻转
                  .turned {
                      box-shadow: 0px 0px 20px 10px #f1c40f;
                      transform: perspective(900px) rotateY(180deg) scale(1) translateY(-50%);
                  }
      
                  // hover卡片放大,游戏结束后失效
                  .cardHover:not(.turned):hover {
                      transform: scale(1.3);
                      filter: none;
                  }
      
                  .back {
                      position: absolute;
                      width: 100%;
                      height: 100%;
                      left: 0;
                      background: rgb(168, 10, 3);
                      transform: rotateY(180deg);
                      font-size: 1rem;
                      color: #f1c40f;
                      border-radius: 10px;
                      display: flex;
                      justify-content: center;
                      align-items: center;
                  }   
                  
                  // 游戏结束,未翻开的牌自动翻开向下移动
                  .gameOver {
                      transform: perspective(900px) rotateY(180deg) scale(1) translateY(50%);
                      cursor: default;
                  }
              }
          }
      </style>

      结语

      以上就是一个翻转福卡的页面,但是在写文章梳理的时候发现有一些逻辑还是比较绕的,可能我自己设置了一些没必要的约束吧。