产品经理:能不能让这串数字滚动起来?
前言
在很多場景下我們需要展示一串數字,這串數字可以是寫死固定在頁面上的,也可以是動態刷新實時請求的,還有一些是根據用戶的交互產生變化的數字。之前我們網站在數字發生變化時是用anime.js做的類似于這樣的一種動畫:
用anime.js做這種動畫的其中一個缺點就是:數字中間不能像上面那張圖一樣有逗號。就可以簡單的理解為必須是number類型的值,字符串'6,000.00'這種就不行。當然我們可以為每一串或者每一個數字單獨應用效果,只不過效果沒有上圖那么好罷了,大概效果類似于這樣:
估計支付寶也是遇見了跟我們同樣的問題,顯示在頁面上的內容看起來像是數字,但實際上確是字符串,只能用類似于split一樣的方法找到逗號和小數點的位置進行分割,然后再把得到的數字字符用parseInt解析成數字類型然后再應用這種效果。
產品那邊對這種效果一直不是很滿意,終于在一次開會時:
產品組:我們本期的主要任務是要優化交互體驗,大家用過餓死了么?他們的軟件在數字這方面有這樣的一種效果:
開發組:你這啥欣賞水平啊?想要改成它們這個*樣?
產品組:當然不是,只是給大家看這種效果,剛才那個效果可能不太好,那我們再給大家換一個頁面看看吧:
開發組:。。。
你要是實在閑的沒事干就去打掃打掃衛生吧!別想起一出是一出了,網站用戶遲早讓你給搞到流失沒了…
產品:哎呀不是!我想要的效果沒有這么難看!只是這個效果讓我想起了老虎機🎰前兩天還在餓死了么里看到過呢!等我找找… 喏!就是這個:
研發:行 知道了 就按照這個效果做是吧?
產品:誒~先別走啊!我還沒說完呢!首先我覺得這個方向不對,咱們要做成像老虎機那種從上往下滾的:
然后不能像餓死了么那效果一樣就那么慢慢的停住…
研發:不慢慢的停住還想咋停住?快快的停住?
產品:不是這個意思,是因為慢慢停住那種效果太普通了,咱們能不能做出來更加動感一點的效果?
研發:你想怎么個動感法?
產品:就是類似于要停住但是速度太快沒能剎住,過去了一點然后再彈回來,你們有什么專業名詞能形容這種效果么?
研發:就是回彈效果唄?
產品:對!就是這個!
研發:可以!沒問題!比你剛給我們看的那個動畫強。
動畫展示
為了防止大家想象不出來具體是什么樣的效果,我們先展示一下已經寫好并且已實際應用在我們頁面上的一些組件:
上面這串數字好像有點不太吉利啊…… 趕緊換一個?
這樣是不是就很有感覺了呢?
這讓我想起以前在中學時看過的一部電影:《奪命手機》男主角靠著一部開了掛的手機進入拉斯維加斯的大賭場,短短幾分鐘之內就瘋狂賺取十萬歐元💶 他來到一部老虎機的面前投幣后按下按鈕,那部老虎機就自帶這種回彈效果:
個人覺得這個回彈效果不夠動感不夠帶勁,所以用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': animate }":style="{ '--i': i, '--delay': delay }"@animationend="animate = false"><ulref="ul":class="{ animate }"><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"><feGaussianBlurin="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))},beforeDestroy () { 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變量的話,可以點擊這篇文章來學習一下?,F在都已經2021年了,是時候學習一下這種技術了,但如果你非要說這玩意IE瀏覽器不支持:
以IE不支持為理由拒絕學習任何新技術的話,那么很快很快,你就會比IE淘汰的還要快。因為就連微軟和Vue3都已經雙雙決定放棄掉IE了:《尤雨溪:Vue3將不會支持IE11 精力會投入到Vue2.7》
用法
這只是一個組件,通常來說我們不會只讓這么一個數字滾動,而是一串數字滾動,我們先定義一個數字886,然后再用computed把886變成[8, 8, 6],最后再v-for一個:
<template><ul class="flex"><ScrollNumv-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"><ScrollNumv-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
結語
怎么樣,是不是效果還不錯呢?現在的你只需要把我的組件復制過去,就能變成自己項目中的一個炫酷小組件啦!
我開源、你開心、老板開法拉利!
- 本文首發于公眾號:《前端學不動》
總結
以上是生活随笔為你收集整理的产品经理:能不能让这串数字滚动起来?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 英维克,上市只是一个新的起点
- 下一篇: python根据参数判断性别准吗_根据数