產品經理:能不能讓這串數字滾動起來?

⚠️本文爲掘金社區首發簽約文章,未獲受權禁止轉載javascript

前言

在不少場景下咱們須要展現一串數字,這串數字能夠是寫死固定在頁面上的,也能夠是動態刷新實時請求的,還有一些是根據用戶的交互產生變化的數字。以前咱們網站在數字發生變化時是用anime.js作的相似於這樣的一種動畫:css

anime.js作這種動畫的其中一個缺點就是:數字中間不能像上面那張圖同樣有逗號。就能夠簡單的理解爲必須是number類型的值,字符串'6,000.00'這種就不行。固然咱們能夠爲每一串或者每個數字單獨應用效果,只不過效果沒有上圖那麼好罷了,大概效果相似於這樣:html

2021-03-15 13-11-56.2021-03-15 13_12_51.gif

估計支付寶也是碰見了跟咱們一樣的問題,顯示在頁面上的內容看起來像是數字但實際上確是字符串,只能用相似於split同樣的方法找到逗號和小數點的位置進行分割,而後再把獲得的數字字符用parseInt解析成數字類型而後再應用這種效果。前端

產品那邊對這種效果一直不是很滿意,終於在一次開會時:vue

產品組:咱們本期的主要任務是要優化交互體驗,你們用過餓死了麼?他們的軟件在數字這方面有這樣的一種效果:java

2021-07-01 16-57-27.2021-07-01 16_57_46.gif

開發組:你這啥欣賞水平啊?想要改爲它們這個*樣?面試

產品組:固然不是,只是給你們看這種效果,剛纔那個效果可能不太好,那咱們再給你們換一個頁面看看吧:vue-router

2021-07-01 17-02-01.2021-07-01 17_02_22.gif

開發組:。。。chrome

你要是實在閒的沒事幹就去打掃打掃衛生吧!別想起一出是一出了,網站用戶早晚讓你給搞到流失沒了…vue-cli

產品:哎呀不是!我想要的效果沒有這麼難看!只是這個效果讓我想起了老虎機🎰前兩天還在餓死了麼裏看到過呢!等我找找… 喏!就是這個:

2021-07-01 17-43-57.2021-07-01 17_44_29.gif

研發:行 知道了 就按照這個效果作是吧?

產品:誒~先別走啊!我還沒說完呢!首先我以爲這個方向不對,我們要作成像老虎機那種從上往下滾的:

ba07c899165dd054dbbb0a3f5c344e51.gif

而後不能像餓死了麼那效果同樣就那麼慢慢的停住…

研發:不慢慢的停住還想咋停住?快快的停住?

產品:不是這個意思,是由於慢慢停住那種效果太普通了,我們能不能作出來更加動感一點的效果?

研發:你想怎麼個動感法?

產品:就是相似於要停住可是速度太快沒能剎住,過去了一點而後再彈回來,大家有什麼專業名詞能形容這種效果麼?

研發:就是回彈效果唄?

產品:對!就是這個!

研發:能夠!沒問題!比你剛給咱們看的那個動畫強。

動畫展現

爲了防止你們想象不出來具體是什麼樣的效果,咱們先展現一下已經寫好而且已實際應用在咱們頁面上的一些組件:

2021-07-06 13-34-13.2021-07-06 13_34_50.2021-07-20 15_00_34.gif

上面這串數字好像有點不太吉利啊…… 趕忙換一個⬇

2021-07-14 20-56-28.2021-07-14 20_56_57.2021-07-20 14_57_07.gif

2021-07-06 13-26-43.2021-07-06 13_27_45.2021-07-20 15_01_25.gif

這樣是否是就頗有感受了呢?

這讓我想起之前在中學時看過的一部電影:《奪命手機》男主角靠着一部開了掛的手機進入拉斯維加斯的大賭場,短短几分鐘以內就瘋狂賺取十萬歐元💶 他來到一部老虎機的面前投幣後按下按鈕,那部老虎機就自帶這種回彈效果:

2021-07-02 13-25-36.2021-07-02 13_26_54.gif

我的以爲這個回彈效果不夠動感不夠帶勁,因此用CSS增強了一下回彈效果,不知你們是喜歡餓死了麼那種無回彈效果、仍是喜歡拉斯維加斯這部老虎機的輕微回彈效果、仍是喜歡本篇文章將要開發出來的動感回彈效果呢?

祝點贊和關注的朋友去賭場玩老虎機時也能像上面那張圖同樣贏大獎💰💰💰

不太小賭怡情 大賭傷身 ☠️ 珍愛生命 遠離賭博 🎲

原理分析

其實這玩意的原理和輪播圖很是類似:

2021-07-05 10-53-04.2021-07-05 10_53_28.gif

一個合格的前端至少也要可以達到會寫輪播圖的水平吧!那麼相信你們對輪播圖的原理應該都不陌生,就是把你要輪播的圖片橫着排列,而後絕對定位,再定義一個表明index的變量,點擊箭頭改變變量的值,再把變量映射到DOMstyle屬性上,最後再用overflow: hidden;隱藏掉露在外面的那些圖:

2021-07-05 11-00-56.2021-07-05 11_01_42.gif

固然這只是個簡易的輪播圖,一個完整的輪播圖底下應該還有一堆小圓點,一方面用來表示一共有多少張圖,另外一方面用來表示當前是第幾張圖。不過這對於咱們要開發的老虎機式滾動數字來講根本用不到,因此暫時就先不寫了。輪播圖不是橫着的嗎?那咱們把給它豎過來試試:

2021-07-05 11-52-25.2021-07-05 11_52_49.gif

接下來再把橋本環奈(輪播圖裏特別可愛的那個小姐姐的名字)的動態圖替換爲數字:

2021-07-05 12-03-18.2021-07-05 12_03_40.gif

作過無限輪播的朋友應該知道,從最後一張到第一張或從第一張到最後一張時爲了看起來像是直接滾動過去,一般會在頭部加上最後一張的複製版、在尾部加入第一張的複製版,咱們這個也不例外,不過因爲咱們不像輪播圖那樣左右均可以滾動,咱們只是從上到下這麼滾,那麼咱們就在下面放上第一張的數字,也就是0。而後去掉箭頭,讓它本身滾:

2021-07-05 12-28-34.2021-07-05 12_29_04.gif

而後再用overflow: hidden;隱藏掉露在外邊的數字:

2021-07-05 12-30-21.2021-07-05 12_30_40.gif

這樣看起來是否是就有點像是這種感受啦:

ba07c899165dd054dbbb0a3f5c344e51.gif

不過還有一個地方不太像,那就是上圖這張老虎機在滾動時自帶模糊效果,會給人一種滾動速度已經快到重影了的錯覺。這一下就讓我想起以前產品經理讓我作的:《鴻蒙那個開場動畫挺帥的 給我們頁面也整一個唄》

2021-06-24 21-36-10.2021-06-24 21_36_26.gif

我知道一提到模糊你們第一時間想到的確定是:filter: blur(幾px);,這個CSS屬性的特色就是會將元素進行全方位模糊。但實際上在有些場景下須要的並非全方位模糊,而是沿着x軸模糊或者沿着y軸模糊,給你們看看用filter: blur();實現出來的效果:

WX20210624-164933.png

而沿着y軸模糊的效果是這樣的:

WX20210705-132916.png

能夠看到效果有着明顯的差別,而恰好咱們想要打到老虎機那種效果須要的也是沿着y軸模糊,那咱們就從那篇文章裏把濾鏡部分的代碼複製過來應用到咱們的頁面上試試:

2021-07-05 14-10-24.2021-07-05 14_10_48.gif

效果好像還不錯!那假如要是用filter: blur();給數字去添加模糊效果會是怎麼樣的一種體驗呢?咱們來試一下:

2021-07-05 14-15-27.2021-07-05 14_15_50.gif

emmmmmmm… 像是得了老花眼…

突發奇想,既然有了這個能夠控制沿x軸仍是沿着y軸模糊的SVG濾鏡,那咱們一樣也能夠把沿着x軸模糊的這一效果應用到輪播圖上去對不對?來試一下:

2021-07-05 18-29-47.2021-07-05 18_30_27.gif

跟以前的輪播圖來個對比:

2021-07-06 09-16-37.2021-07-06 09_16_56.gif

怎麼樣?是否是在加上了這個濾鏡以後輪播圖就顯得更加動感了呢?

動畫定位

假如咱們想要讓數字定位到6這個數字:

2021-07-08 10-47-28.2021-07-08 10_48_03.gif

不過這個6還帶有咱們添加的上下模糊效果,咱們在停住時把模糊濾鏡去掉再來看看:

2021-07-08 10-52-39.2021-07-08 10_53_07.gif

是否是看起來好像恰巧就是滾動到6這個位置停下來的同樣啊?但實際上並非這樣,而是這樣:

2021-07-08 10-56-32.2021-07-08 10_57_34.gif

仔細觀察的話能夠發現其實並非那串數字恰巧滾動到6這個位置而後停住的,而是無論滾動到哪,只要是到了時間就直接定位到6。若是看不太清楚的話咱們放慢速度、給6加入一個紅色背景後再來看一眼:

2021-07-08 12-43-29.2021-07-08 12_43_52.gif

因爲滾動速度快,因此即使沒有滾動到了第6位數字就忽然在第6位數字停住,人眼也看不出來,反而會以爲就是滾動到了6這個數字的面前,其實也就是障眼法CSS有不少特效都是靠着相似於障眼法同樣的方式去實現的,比方說無限滾動的輪播圖,看起來就像是真的有無數張圖片鏈接在一塊兒同樣。這有點相似於魔術,都是在用一些小技巧去欺騙用戶的眼睛,從而達到使人稱讚的效果。這也是我爲何會喜歡炫酷CSS特效的緣由,感受本身就像是在網頁裏的魔術師,爲你們表演了一段魔術同樣。

不過確定有人會問,這樣作有什麼好處嗎?爲何不作成直接滾動到對應的數字再停住啊:

2021-07-08 13-08-47.2021-07-08 13_09_08.gif

首先,不管是作成這樣仍是作成那樣,他倆最終的效果差很少,都長成這樣:

2021-07-08 10-52-39.2021-07-08 10_53_07.gif

除非你家產品經理要求滾動的速度像蝸牛同樣慢吞吞的,不然根本就看不出來有什麼區別。而另外一個緣由則是這樣能夠方便咱們可以精確控制在什麼時間中止滾動。好比說咱們設置了在幾秒鐘以後中止滾動,那麼到了中止滾動的這個時間時它到底滾在了第幾位是不肯定的對吧?假如咱們想在第9位停住,可是到時間時動畫恰巧處在第1位,那麼動畫還要繼續進行滾動,直到第9位時纔可以停下來。假設咱們本來設置的是滾動兩秒鐘,而從第1位滾動到第9位須要耗時0.8秒鐘,那麼最終整個動畫實際上是滾動了2.8秒才停下,與咱們所設置的兩秒鐘明顯不符。

不過聰明的同窗確定會想到:你不默認從第0位開始滾不就得了嘛!而是根據你傳入的數字來動態計算應該從第幾位開始滾。好比你計劃滾動2秒鐘,而後在滾動到第6位時停住,那麼只須要計算從第幾位開始滾,兩秒鐘以後它恰巧就能滾到第6位不就完事了嘛!

這樣作確實是可行的,但這無疑會增長咱們代碼的複雜度,而效果卻又差很少,還會浪費掉咱們好幾根頭髮去進行計算,其實咱們明明有更簡單的實現方式,那就是:把動畫分爲兩段去運行!

分段式動畫

第一段動畫

2021-07-05 14-10-24.2021-07-05 14_10_48.gif

也就是無限滾動動畫,咱們會封裝成組件,具體滾動多久由傳入的參數決定。

第二段動畫

2021-07-08 14-47-56.2021-07-08 14_48_31.gif

能夠看到最終咱們會選擇一個數字來作這樣的動感回彈效果,無限滾動完就立馬切換到這個動畫上面去,具體是哪一個數字也是由傳進來的參數決定的。

連起來

2021-07-08 15-15-13.2021-07-08 15_15_53.gif

組件代碼

因爲這個項目是用Vue2.x來進行製做的,因此貼出來的代碼也是Vue2的風格,不過不要緊,JS部分很簡單,主要代碼都集中在CSS部分了。因此你們能夠很輕鬆的將這個組件改爲符合本身項目的Vue3.x組件或者React組件等:

<template>
  <component :is="as" class="scroll-num" :class="{ 'border-animate': showAnimate }" :style="{ '--i': i, '--delay': delay }" @animationend="showAnimate = false" >
    <ul ref="ul" :class="{ animate: showAnimate }" >
      <li>0</li>
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
      <li>5</li>
      <li>6</li>
      <li>7</li>
      <li>8</li>
      <li>9</li>
      <li>0</li>
    </ul>

    <svg width="0" height="0">
      <filter id="blur">
        <feGaussianBlur in="SourceGraphic" :stdDeviation="`0 ${blur}`" />
      </filter>
    </svg>
  </component>
</template>

<script> export default { name: 'ScrollNum', props: { as: { type: String, default: 'div' }, i: { type: Number, default: 0, validator: v => v < 10 && v >= 0 && Number.isInteger(v) }, delay: { type: Number, default: 1 }, blur: { type: Number, default: 2 } }, data: () => ({ timer: null, showAnimate: true }), watch: { i () { this.showAnimate = true } }, mounted () { const ua = navigator.userAgent.toLowerCase() const testUA = regexp => regexp.test(ua) const isSafari = testUA(/safari/g) && !testUA(/chrome/g) // Safari瀏覽器的兼容代碼 isSafari && (this.timer = setTimeout(() => { this.$refs.ul.setAttribute('style', ` animation: none; transform: translateY(calc(var(--i) * -9.09%)) `) }, this.delay * 1000)) }, beforeUnmount () { clearTimeout(this.timer) } } </script>

<style scoped> .scroll-num { width: var(--width, 20px); height: var(--height, calc(var(--width, 20px) * 1.8)); color: var(--color, #333); font-size: var(--height, calc(var(--width, 20px) * 1.1)); line-height: var(--height, calc(var(--width, 20px) * 1.8)); text-align: center; overflow: hidden; } .animate { animation: move .3s linear infinite, bounce-in-down 1s calc(var(--delay) * 1s) forwards } .border-animate { animation: enhance-bounce-in-down 1s calc(var(--delay) * 1s) forwards } ul { padding: 0; margin: 0; list-style: none; transform: translateY(calc(var(--i) * -9.09%)); } @keyframes move { from { transform: translateY(-90%); filter: url(#blur) } to { transform: translateY(1%); filter: url(#blur) } } @keyframes bounce-in-down { from { transform: translateY(calc(var(--i) * -9.09% - 7%)); filter: none } 25% { transform: translateY(calc(var(--i) * -9.09% + 3%)) } 50% { transform: translateY(calc(var(--i) * -9.09% - 1%)) } 70% { transform: translateY(calc(var(--i) * -9.09% + .6%)) } 85% { transform: translateY(calc(var(--i) * -9.09% - .3%)) } to { transform: translateY(calc(var(--i) * -9.09%)) } } @keyframes enhance-bounce-in-down { 25% { transform: translateY(8%) } 50% { transform: translateY(-4%) } 70% { transform: translateY(2%) } 85% { transform: translateY(-1%) } to { transform: translateY(0) } } </style>

複製代碼

⚠️ 若是把這個組件複製到項目中去 發現樣式顯示不正確的話,只須要解開CSS部分ul裏註釋掉的樣式便可。出現這種現象的緣由是你沒有引入reset.css,致使ul標籤有默認的邊距、li標籤有默認的小圓點。若是有reset.css的話,就刪掉這段沒用的註釋。

這個組件封裝的思路主要是用到了CSS變量+calc函數來控制滾動時長,在不傳--width--height寬高的狀況下默認會是20 * 36,還能夠只傳寬不傳高,利用calc函數能保證在只傳寬度的狀況下,高度依然可以保持住原有的比例。我知道這時確定會有人說:想要保持比例用aspect-ratio就好了,何須那麼麻煩呢?


首先就是這個屬性比較,兼容性還不是特別好,雖然Edge火狐谷歌的最新幾個版本都已經支持這一屬性了,但Safari瀏覽器只有15-技術預覽版才支持,而在IOS下則是徹底不支持:

image.png

要知道用iPhone的用戶大多數都會選擇Safari,由於他們也不懂什麼各類瀏覽器啥的,只知道點這個指南針🧭同樣的圖標是用來上網的。另外一點則是咱們其實並非非要保持住這個比例,這是隻是我封裝組件的一個習慣。有時候懶,但願用組件時只傳一個寬或者高就得了,沒傳的那個參數可以自動計算,因此纔會封裝成這個樣子。你能夠按照本身的喜愛來,把那段代碼改爲你喜歡的樣子。


若是不太清楚什麼是CSS變量的話,能夠點擊這篇文章來學習一下。如今都已經2021年了,是時候學習一下這種技術了,但若是你非要說這玩意IE瀏覽器不支持:

WX20210709-165227.png

IE不支持爲理由拒絕學習任何新技術的話,那麼很快很快,你就會比IE淘汰的還要快。由於就連微軟Vue3都已經雙雙決定放棄掉IE了:《尤雨溪:Vue3將不會支持IE11 精力會投入到Vue2.7》

用法

這只是一個組件,一般來講咱們不會只讓這麼一個數字滾動,而是一串數字滾動,咱們先定義一個數字886,而後再用computed886變成[8, 8, 6],最後再v-for一個:

<template>
  <ul class="flex">
    <ScrollNum v-for="(num, idx) of numArr" :key="idx" as="li" :i="num" :delay="idx + 1" />
  </ul>
</template>

<script> import ScrollNum from './components/ScrollNum.vue' export default { name: 'App', components: { ScrollNum }, data: () => ({ num: 886 }), computed: { numArr () { const str = String(this.num) let arr = [] for (let i = 0; i < str.length; i++) { arr.push(parseInt(str[i])) } return arr } }, mounted () { setInterval(() => this.num++, 10000) } } </script>

<style scoped> .flex { display: flex; } ul { padding: 0; margin: 0; list-style: none; } </style>

複製代碼

一個完美的老虎機效果就這樣完成啦:

2021-07-12 16-48-02.2021-07-12 16_48_47.gif

若是想要調整大小的話,只須要給它一個--width,高度和字體大小就會自動進行調整。咱們還能夠再加上一個邊框:

<template>
  <ul class="flex">
    <ScrollNum v-for="(number, idx) of numArr" :key="idx" :i="number" :delay="idx + 2.5" as="li" class="num" />
  </ul>
</template>

<script> import ScrollNum from './components/ScrollNum.vue' export default { name: 'App', components: { ScrollNum }, data: () => ({ num: 886 }), computed: { numArr () { const str = String(this.num) let arr = [] for (let i = 0; i < str.length; i++) { arr.push(parseInt(str[i])) } return arr } }, mounted () { setInterval(() => this.num++, 10000) } } </script>

<style> .flex { display: flex; } ul { padding: 0; margin: 0; list-style: none; } .num { --width: 26px; margin-right: 6px; border: 1px solid black; border-radius: 8px } </style>

複製代碼

2021-07-12 16-52-24.2021-07-12 16_52_57.gif

⚠️ 若是你複製個人代碼到本身項目中發現滾動沒法停住的話,多是vue-loader版本太低致使的編譯scoped多重動畫時致使的bug 建議升級vue-cli或者去掉<style scoped>上的scoped,而後給DOM起一個不容易重名的類名或ID

結語

怎麼樣,是否是效果還不錯呢?如今的你只須要把個人組件複製過去,就能變成本身項目中的一個炫酷小組件啦!

我開源、你開心、老闆開法拉利!

往期精彩文章

相關文章
相關標籤/搜索