翻書動畫組件(vue2 + sass)

前言

之前在網(wǎng)上看到一個翻書動畫的實現(xiàn)绪励,覺得非常巧妙陕悬,借此機會記錄練習(xí)一下灌诅。
前端框架 vue 提供了模板式開發(fā),利用 vue 語法能更方便地創(chuàng)造和操作 dom单刁,而且非常方便與 css 預(yù)處理語言 sass 集成灸异,后期也非常方便地與實際項目進行集成府适。
此外項目還對移動端進行了適配,根據(jù)不同屏幕尺寸大小進行調(diào)整肺樟。

準備工作

1.使用 vue-cli 3.0 創(chuàng)建一個 vue 項目檐春。
2.在創(chuàng)建完成的項目下導(dǎo)入素材到 src/assets/images

第一部分

翻書動畫,先來看看成品效果:

翻書動畫.gif

分析一下布局:
我們定義了一個 flapCardList 對象存儲了所有卡片的背景圖片和背景 r,g,b 值么伯,還有旋轉(zhuǎn)的角度等:

export const flapCardList = [
    {
      r: 255,
      g: 102,
      _g: 102,
      b: 159,
      imgLeft: 'url(' + require('@/assets/images/gift-left.png') + ')',
      imgRight: 'url(' + require('@/assets/images/gift-right.png') + ')',
      backgroundSize: '50% 50%',
      zIndex: 100,
      rotateDegree: 0
    },
  ···
]

這樣我們就可以根據(jù) flapCardList 的內(nèi)容來循環(huán)創(chuàng)建所有動畫卡片的 dom 并且動態(tài)控制疟暖。直接看這個布局結(jié)構(gòu),逐層嵌套田柔,很好理解俐巴,重點是如何靠 css 來實現(xiàn)實際的效果。由于采用了 vue + scss 的方案硬爆,我們可以把公共的布局抽象出來便于調(diào)用欣舵。具體的做法是使用 scss 的 mixin 機制,這塊大家可以在源碼中具體去觀察下做法缀磕。接下里的步驟里會逐步分析實現(xiàn)過程缘圈,同時也會包含 css 的控制。我們先簡單地預(yù)覽下完整的 dom 的結(jié)構(gòu)袜蚕,給左右兩個小卡片綁定了 css 設(shè)置方法 semiCircleStyle糟把,用來設(shè)置背景圖片,背景顏色廷没,尺寸糊饱。

<template>
  <div class="flap-card-wrapper">
    <div class="flap-card-bg">
      <div class="flap-card" v-for="(item, index) in flapCardList" :key="index" :style="{zIndex: item.zIndex}">
        <div class="flap-card-circle">
          <div class="flap-card-semi-circle flap-card-semi-circle-left" :style="semiCircleStyle(item, 'left')" ref="left">
          </div>
          <div class="flap-card-semi-circle flap-card-semi-circle-right" :style="semiCircleStyle(item, 'right')" ref="right">
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

接著分析動畫的過程,有別于 canvs api 的繪圖颠黎,css 呈現(xiàn)的效果只是 2D 的平面效果另锋,沒法做到如 three.js, ht.js 等基于 webGL 庫所繪制出來的真實 3D 場景,因而我們永遠需要去控制顯示的層級才能實現(xiàn)效果狭归,為什么說這個呢夭坪,因為我們的卡片翻轉(zhuǎn)只能從正面看,因而當前面卡片翻轉(zhuǎn)后需要使后面卡片的 z-index 屬性大于翻轉(zhuǎn)過的卡片才能看到过椎。

觀察上面的 gif 圖片我們嘗試總結(jié)一下動畫的步驟:
1.前面的卡片翻轉(zhuǎn)室梅,前面的卡片轉(zhuǎn)動的角度到達 90 度的時候隱藏。
2.這個時候疚宇,左邊部分顯示背面的轉(zhuǎn)動亡鼠。
3.當轉(zhuǎn)動角度達到 180 度時,一個卡片翻轉(zhuǎn)完敷待,繼續(xù)下一張卡片的翻轉(zhuǎn)间涵。

這么分析下來其實并不知道該如何下手,我們試試想下現(xiàn)在能做什么榜揖,先寫一個函數(shù)讓第一個卡片動起來吧勾哩。
先說明一下 rotateY 這個屬性抗蠢,與 rotateX 和 rotateZ 不同,rotateY 正方向為逆時針思劳,在網(wǎng)頁里就是從右邊向屏幕這個方向迅矛,反之亦然。
旋轉(zhuǎn)本質(zhì)上是改變對象的 rotateY潜叛,通過定時器進行值的變化:

 rotate(index, type) {
        let item = this.flapCardList[index   ]
        let dom = type === 'front' ? this.$refs.right[index] : this.$refs.left[index]
        dom.style.transform = `rotateY(${item.rotateDegree}deg)`
        dom.style.backgroundColor = `rgb(${item.r}, ${item._g}, ${item.b})`
    },
    startFlapAnimation() {
      setInterval(() => {
        const frontFlapCard = this.flapCardList[this.front]
        frontFlapCard.rotateDegree += 10
        this.rotate(0, 'right')
      }, this.flapInterval)
    }

在 mounted 里開啟動畫:

 mounted() {
    this.startFlapAnimation()
  }

效果預(yù)覽:

1.gif

旋轉(zhuǎn)沒有繞著中心軸秽褒,添加 css 屬性:

   .flap-card-semi-circle-left {
          border-radius: px2rem(24) 0 0 px2rem(24);
          background-position: center right;
          transform-origin: right;
        }
        .flap-card-semi-circle-right {
          border-radius: 0 px2rem(24) px2rem(24) 0;
          background-position: center left;
          transform-origin: left;
        }

運行起來發(fā)現(xiàn)有有兩個問題,一個是正面轉(zhuǎn)過 90 度的時候沒有隱藏威兜,一個是背面旋轉(zhuǎn)震嫉。
隱藏可以使用設(shè)置背面的時候隱藏。

 .flap-card-semi-circle {
    flex: 0 0 50%;
    width: 50%;
    height: 100%;
    background-repeat: no-repeat;
    backface-visibility: hidden;
}

這個時候再運行就可以了牡属。

這時如果像第一張卡片旋轉(zhuǎn)這樣去設(shè)置背面卡片的運動是無法顯示的,因為背面是左邊轉(zhuǎn)動扼睬,由于 z-index 比較小逮栅,所以會永遠被左邊的覆蓋。
這個時候就需要在臨界值轉(zhuǎn)過 90 度的時候改變背面卡片的 z-index 值窗宇,正面卡片轉(zhuǎn)過 90 度措伐, 背面卡片也需要跟著轉(zhuǎn),這時就需要在轉(zhuǎn)動之前先讓背面卡片轉(zhuǎn)過 180 度與右半部分重合军俊,然后在反方向旋轉(zhuǎn)侥加,當轉(zhuǎn)過 90 度的時候就與正面卡片隔著屏幕對稱了,這個時候繼續(xù)運動就可以得到漸入的效果粪躬, 逐漸覆蓋前面的卡片担败,具體的實現(xiàn)如下:

// 下一張卡片動作前先做準備
    prepare() {
      const backFlapCard = this.flapCardList[this.back]
      backFlapCard.rotateDegree = 180
      this.rotate(this.back, 'back')
    },
    startFlapAnimation() {
      // 開始就需要先預(yù)制一次
      this.prepare()
      setInterval(() => {
        const frontFlapCard = this.flapCardList[this.front]
        const backFlapCard = this.flapCardList[this.back]
        // this.prepare()
        frontFlapCard.rotateDegree += 10
        backFlapCard.rotateDegree -= 10
        if (frontFlapCard.rotateDegree === 90 && backFlapCard.rotateDegree === 90) {
          backFlapCard.zIndex += 2
        }
        this.rotate(this.front, 'front')
        this.rotate(this.back, 'back')
      }, this.flapInterval)

預(yù)覽下效果:


2.gif

接下來需要把所有的卡片加入循環(huán),除了需要 this.front || back 這兩個計數(shù)需要增加镰官,我們還需要做幾個操作:
1.把已經(jīng)旋轉(zhuǎn)過的卡片角度設(shè)置為原來的角度提前,也就是 0;
2.旋轉(zhuǎn) 180 度進行一次切換泳唠,z-index 需要輪換:
100 = 96
99 = 97
98 = 96
97 = 99
96 = 100

具體實現(xiàn):

    next() {
      // 重置狀態(tài)
      const frontFlapCard = this.flapCardList[this.front]
      const backFlapCard = this.flapCardList[this.back]
      frontFlapCard.rotateDegree = 0
      backFlapCard.rotateDegree = 0
      this.rotate(this.front, 'front')
      this.rotate(this.back, 'back')
      // 計數(shù)增加
      const len = this.flapCardList.length

      this.front++
      this.back++
      if (this.front >= len) {
        this.front = 0
      } 
      if (this.back >= len) {
        this.back = 0
      }
      // 輪換 zIndex
      this.flapCardList.forEach((item, index) => {
          item.zIndex = 100 - ((index - this.front + len) % len)
      })
      this.prepare()
    },
    startFlapAnimation() {
      // 開始就需要先預(yù)制一次
      this.prepare()
      setInterval(() => {
        const frontFlapCard = this.flapCardList[this.front]
        const backFlapCard = this.flapCardList[this.back]
        frontFlapCard.rotateDegree += 10
        backFlapCard.rotateDegree -= 10
        if (frontFlapCard.rotateDegree === 90 && backFlapCard.rotateDegree === 90) {
          backFlapCard.zIndex += 2
        }
        this.rotate(this.front, 'front')
        this.rotate(this.back, 'back')
        if (frontFlapCard.rotateDegree === 180 && backFlapCard.rotateDegree === 0) {
          this.next()
        }
      }, this.flapInterval)
    }

再看下效果:


3.gif

可以看到效果基本實現(xiàn)狈网,這里還可以進一步地優(yōu)化下效果,在前面卡片轉(zhuǎn)動的過程中顏色逐漸加深笨腥,后面卡片轉(zhuǎn)動的時候顏色逐漸變淺拓哺,這個做法也很簡單,通過動態(tài)地設(shè)置 backgroundColor 的 _g 分量值脖母。需要注意的是背面卡片提前轉(zhuǎn)過了 180 度士鸥,直到背面卡片到 0 的時候轉(zhuǎn)過了 18 次,需要提前加深響應(yīng)的顏色:

  prepare() {
      const backFlapCard = this.flapCardList[this.back]
      backFlapCard.rotateDegree = 180
      this.rotate(this.back, 'back')
      backFlapCard._g = backFlapCard.g + 5 * 18
    }

然后在 next 函數(shù)里把之前的顏色重置:

frontFlapCard._g = frontFlapCard.g 
backFlapCard._g = backFlapCard.g

至此一個基本的效果就完成了镶奉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末础淤,一起剝皮案震驚了整個濱河市崭放,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸽凶,老刑警劉巖币砂,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異玻侥,居然都是意外死亡决摧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門凑兰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掌桩,“玉大人,你說我怎么就攤上這事姑食〔ǖ海” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵音半,是天一觀的道長则拷。 經(jīng)常有香客問我,道長曹鸠,這世上最難降的妖魔是什么煌茬? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮彻桃,結(jié)果婚禮上坛善,老公的妹妹穿的比我還像新娘。我一直安慰自己邻眷,他們只是感情好眠屎,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耗溜,像睡著了一般组力。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抖拴,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天燎字,我揣著相機與錄音,去河邊找鬼阿宅。 笑死候衍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的洒放。 我是一名探鬼主播蛉鹿,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼往湿!你這毒婦竟也來了妖异?” 一聲冷哼從身側(cè)響起惋戏,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎他膳,沒想到半個月后响逢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡棕孙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年舔亭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟀俊。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡钦铺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肢预,到底是詐尸還是另有隱情矛洞,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布烫映,位于F島的核電站缚甩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏窑邦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一壕探、第九天 我趴在偏房一處隱蔽的房頂上張望冈钦。 院中可真熱鬧,春花似錦李请、人聲如沸瞧筛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽较幌。三九已至,卻和暖如春白翻,著一層夾襖步出監(jiān)牢的瞬間乍炉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工滤馍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留岛琼,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓巢株,卻偏偏與公主長得像槐瑞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阁苞,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 第一部分:變形介紹 自層疊樣式表誕生以來困檩,元素始終是矩形的祠挫,而且只能沿著橫軸和縱軸放置。有些技巧能讓元素看起來是傾...
    俠客有情劍無情QAQ閱讀 1,488評論 0 17
  • 之前有看到過很多基于HTML5或者CSS3制作的愛心動畫悼沿,不過基本上都是2D平面的等舔,今天在國外的網(wǎng)站上看到一個基于...
    編程鴨閱讀 843評論 0 0
  • 看了很多視頻、文章显沈,最后卻通通忘記了软瞎,別人的知識依舊是別人的,自己卻什么都沒獲得拉讯。此系列文章旨在加深自己的印象涤浇,因...
    DCbryant閱讀 1,857評論 0 4
  • 2 在那之后的一年,我趕著學(xué)習(xí)魔慷、進修只锭,再也沒有碰到過張悠悠。 隔年五月院尔,我在豪生酒店舉辦了一次免費的心理分享課蜻展,參...
    青蔥VS扶貝閱讀 438評論 0 0
  • 野地里荒草連連。走累了邀摆,就地躺下纵顾,反正也沒人。閉上眼栋盹,睡著了施逾,然后猛的醒來,不知道過了多久例获。 這是一段廢棄的公路汉额,...
    秦斌元閱讀 235評論 0 0