⚠️本文爲掘金社區首發簽約文章,未獲受權禁止轉載javascript
在不少場景下咱們須要展現一串數字,這串數字能夠是寫死固定在頁面上的,也能夠是動態刷新實時請求的,還有一些是根據用戶的交互產生變化的數字。以前咱們網站在數字發生變化時是用anime.js
作的相似於這樣的一種動畫:css
用anime.js
作這種動畫的其中一個缺點就是:數字中間不能像上面那張圖同樣有逗號。就能夠簡單的理解爲必須是number
類型的值,字符串'6,000.00'
這種就不行。固然咱們能夠爲每一串或者每個數字單獨應用效果,只不過效果沒有上圖那麼好罷了,大概效果相似於這樣:html
估計支付寶也是碰見了跟咱們一樣的問題,顯示在頁面上的內容看起來像是數字
,但實際上確是字符串
,只能用相似於split
同樣的方法找到逗號和小數點的位置進行分割,而後再把獲得的數字字符用parseInt
解析成數字類型而後再應用這種效果。前端
產品那邊對這種效果一直不是很滿意,終於在一次開會時:vue
產品組:咱們本期的主要任務是要優化交互體驗,你們用過餓死了麼
?他們的軟件在數字這方面有這樣的一種效果:java
開發組:你這啥欣賞水平啊?想要改爲它們這個*
樣?面試
產品組:固然不是,只是給你們看這種效果,剛纔那個效果可能不太好,那咱們再給你們換一個頁面看看吧:vue-router
開發組:。。。chrome
你要是實在閒的沒事幹就去打掃打掃衛生吧!別想起一出是一出了,網站用戶早晚讓你給搞到流失沒了…vue-cli
產品:哎呀不是!我想要的效果沒有這麼難看!只是這個效果讓我想起了老虎機🎰前兩天還在餓死了麼
裏看到過呢!等我找找… 喏!就是這個:
研發:行 知道了 就按照這個效果作是吧?
產品:誒~先別走啊!我還沒說完呢!首先我以爲這個方向不對,我們要作成像老虎機那種從上往下滾的:
而後不能像餓死了麼
那效果同樣就那麼慢慢的停住…
研發:不慢慢的停住還想咋停住?快快的停住?
產品:不是這個意思,是由於慢慢停住那種效果太普通了,我們能不能作出來更加動感一點的效果?
研發:你想怎麼個動感法?
產品:就是相似於要停住可是速度太快沒能剎住,過去了一點而後再彈回來,大家有什麼專業名詞能形容這種效果麼?
研發:就是回彈效果唄?
產品:對!就是這個!
研發:能夠!沒問題!比你剛給咱們看的那個動畫強。
爲了防止你們想象不出來具體是什麼樣的效果,咱們先展現一下已經寫好而且已實際應用在咱們頁面上的一些組件:
上面這串數字好像有點不太吉利啊…… 趕忙換一個⬇
這樣是否是就頗有感受了呢?
這讓我想起之前在中學時看過的一部電影:《奪命手機》
男主角靠着一部開了掛的手機進入拉斯維加斯的大賭場,短短几分鐘以內就瘋狂賺取十萬歐元💶 他來到一部老虎機的面前投幣後按下按鈕,那部老虎機就自帶這種回彈效果:
我的以爲這個回彈效果不夠動感不夠帶勁,因此用CSS增強了一下回彈效果,不知你們是喜歡餓死了麼
那種無回彈效果、仍是喜歡拉斯維加斯
這部老虎機的輕微回彈效果、仍是喜歡本篇文章
將要開發出來的動感回彈效果呢?
祝點贊和關注的朋友去賭場玩老虎機時也能像上面那張圖同樣贏大獎💰💰💰
不太小賭怡情 大賭傷身 ☠️ 珍愛生命 遠離賭博 🎲
其實這玩意的原理和輪播圖很是類似:
一個合格的前端至少也要可以達到會寫輪播圖的水平吧!那麼相信你們對輪播圖的原理應該都不陌生,就是把你要輪播的圖片橫着排列,而後絕對定位,再定義一個表明index
的變量,點擊箭頭改變變量的值,再把變量映射到DOM
的style
屬性上,最後再用overflow: hidden;
隱藏掉露在外面的那些圖:
固然這只是個簡易的輪播圖,一個完整的輪播圖底下應該還有一堆小圓點,一方面用來表示一共有多少張圖,另外一方面用來表示當前是第幾張圖。不過這對於咱們要開發的老虎機式滾動數字來講根本用不到,因此暫時就先不寫了。輪播圖不是橫着的嗎?那咱們把給它豎過來試試:
接下來再把橋本環奈
(輪播圖裏特別可愛的那個小姐姐的名字)的動態圖替換爲數字:
作過無限輪播的朋友應該知道,從最後一張到第一張或從第一張到最後一張時爲了看起來像是直接滾動過去,一般會在頭部加上最後一張的複製版、在尾部加入第一張的複製版,咱們這個也不例外,不過因爲咱們不像輪播圖那樣左右均可以滾動,咱們只是從上到下這麼滾,那麼咱們就在下面放上第一張的數字,也就是0
。而後去掉箭頭,讓它本身滾:
而後再用overflow: hidden;
隱藏掉露在外邊的數字:
這樣看起來是否是就有點像是這種感受啦:
不過還有一個地方不太像,那就是上圖這張老虎機在滾動時自帶模糊效果,會給人一種滾動速度已經快到重影了的錯覺。這一下就讓我想起以前產品經理讓我作的:《鴻蒙那個開場動畫挺帥的 給我們頁面也整一個唄》
我知道一提到模糊你們第一時間想到的確定是:filter: blur(幾px);
,這個CSS屬性的特色就是會將元素進行全方位模糊。但實際上在有些場景下須要的並非全方位模糊,而是沿着x
軸模糊或者沿着y
軸模糊,給你們看看用filter: blur();
實現出來的效果:
而沿着y
軸模糊的效果是這樣的:
能夠看到效果有着明顯的差別,而恰好咱們想要打到老虎機那種效果須要的也是沿着y
軸模糊,那咱們就從那篇文章裏把濾鏡部分的代碼複製過來應用到咱們的頁面上試試:
效果好像還不錯!那假如要是用filter: blur();
給數字去添加模糊效果會是怎麼樣的一種體驗呢?咱們來試一下:
emmmmmmm… 像是得了老花眼…
突發奇想,既然有了這個能夠控制沿x
軸仍是沿着y
軸模糊的SVG
濾鏡,那咱們一樣也能夠把沿着x
軸模糊的這一效果應用到輪播圖上去對不對?來試一下:
跟以前的輪播圖來個對比:
怎麼樣?是否是在加上了這個濾鏡以後輪播圖就顯得更加動感了呢?
假如咱們想要讓數字定位到6
這個數字:
不過這個6
還帶有咱們添加的上下模糊效果,咱們在停住時把模糊濾鏡去掉再來看看:
是否是看起來好像恰巧就是滾動到6
這個位置停下來的同樣啊?但實際上並非這樣,而是這樣:
仔細觀察的話能夠發現其實並非那串數字恰巧滾動到6
這個位置而後停住的,而是無論滾動到哪,只要是到了時間就直接定位到6
。若是看不太清楚的話咱們放慢速度、給6
加入一個紅色背景後再來看一眼:
因爲滾動速度快,因此即使沒有滾動到了第6
位數字就忽然在第6
位數字停住,人眼也看不出來,反而會以爲就是滾動到了6
這個數字的面前,其實也就是障眼法
。CSS
有不少特效都是靠着相似於障眼法同樣的方式去實現的,比方說無限滾動的輪播圖,看起來就像是真的有無數張圖片鏈接在一塊兒同樣。這有點相似於魔術,都是在用一些小技巧去欺騙用戶的眼睛,從而達到使人稱讚的效果。這也是我爲何會喜歡炫酷CSS
特效的緣由,感受本身就像是在網頁裏的魔術師
,爲你們表演了一段魔術同樣。
不過確定有人會問,這樣作有什麼好處嗎?爲何不作成直接滾動到對應的數字再停住啊:
首先,不管是作成這樣仍是作成那樣,他倆最終的效果差很少,都長成這樣:
除非你家產品經理要求滾動的速度像蝸牛同樣慢吞吞的,不然根本就看不出來有什麼區別。而另外一個緣由則是這樣能夠方便咱們可以精確控制在什麼時間中止滾動。好比說咱們設置了在幾秒鐘以後中止滾動,那麼到了中止滾動的這個時間時它到底滾在了第幾位是不肯定的對吧?假如咱們想在第9
位停住,可是到時間時動畫恰巧處在第1
位,那麼動畫還要繼續進行滾動,直到第9
位時纔可以停下來。假設咱們本來設置的是滾動兩秒鐘,而從第1
位滾動到第9
位須要耗時0.8
秒鐘,那麼最終整個動畫實際上是滾動了2.8
秒才停下,與咱們所設置的兩秒鐘明顯不符。
不過聰明的同窗確定會想到:你不默認從第0
位開始滾不就得了嘛!而是根據你傳入的數字來動態計算應該從第幾位開始滾。好比你計劃滾動2
秒鐘,而後在滾動到第6
位時停住,那麼只須要計算從第幾位開始滾,兩秒鐘以後它恰巧就能滾到第6
位不就完事了嘛!
這樣作確實是可行的,但這無疑會增長咱們代碼的複雜度,而效果卻又差很少,還會浪費掉咱們好幾根頭髮去進行計算,其實咱們明明有更簡單的實現方式,那就是:
把動畫分爲兩段去運行!
也就是無限滾動動畫,咱們會封裝成組件,具體滾動多久由傳入的參數決定。
能夠看到最終咱們會選擇一個數字來作這樣的動感回彈效果,無限滾動完就立馬切換到這個動畫上面去,具體是哪一個數字也是由傳進來的參數決定的。
因爲這個項目是用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
下則是徹底不支持:
要知道用iPhone
的用戶大多數都會選擇Safari
,由於他們也不懂什麼各類瀏覽器啥的,只知道點這個指南針🧭同樣的圖標是用來上網的。另外一點則是咱們其實並非非要保持住這個比例,這是隻是我封裝組件的一個習慣。有時候懶,但願用組件時只傳一個寬或者高就得了,沒傳的那個參數可以自動計算,因此纔會封裝成這個樣子。你能夠按照本身的喜愛來,把那段代碼改爲你喜歡的樣子。
若是不太清楚什麼是CSS變量的話,能夠點擊這篇文章來學習一下。如今都已經2021
年了,是時候學習一下這種技術了,但若是你非要說這玩意IE
瀏覽器不支持:
以IE
不支持爲理由拒絕學習任何新技術的話,那麼很快很快,你就會比IE
淘汰的還要快。由於就連微軟
和Vue3
都已經雙雙決定放棄掉IE
了:《尤雨溪:Vue3將不會支持IE11 精力會投入到Vue2.7》
這只是一個組件,一般來講咱們不會只讓這麼一個數字滾動,而是一串數字滾動,咱們先定義一個數字886
,而後再用computed
把886
變成[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>
複製代碼
一個完美的老虎機效果就這樣完成啦:
若是想要調整大小的話,只須要給它一個--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>
複製代碼
⚠️ 若是你複製個人代碼到本身項目中發現滾動沒法停住的話,多是
vue-loader
版本太低致使的編譯scoped
多重動畫時致使的bug
建議升級vue-cli
或者去掉<style scoped>
上的scoped
,而後給DOM
起一個不容易重名的類名或ID
怎麼樣,是否是效果還不錯呢?如今的你只須要把個人組件複製過去,就能變成本身項目中的一個炫酷小組件啦!
我開源、你開心、老闆開法拉利!