日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

html时钟翻牌效果,干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)

發布時間:2024/4/13 vue 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 html时钟翻牌效果,干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

雙十一剁手節過去了,大家應該在很多網頁中看到了數字翻牌的效果吧,比如倒計時、

數字增長等。相信很多人都已經自己獨立實現過了,我也在網上看了一些demo,發現HTML結構大多比較復雜,用了4個并列的標簽來放置前后兩個“牌”。本文就來講解下,如何進一步精簡HTML,讓結構簡單,讓JS方法封裝得易使用。先來看看最終效果:

每個翻牌的HTML結構(精簡至2個并列標簽):

復制代碼

本次分享含有很多小技巧,靈活使用能夠提升技術水平和工作效率,具體包括以下知識點:

知識點1::before :after偽元素的使用

知識點2:line-height: 0的妙用

知識點3:transform-origin和perspective

知識點4:backface-visibility

知識點5:時間格式化函數的實現

Let's do it!

1 翻牌的構建

1.1 基本結構

首先解釋下HTML的結構:

復制代碼

【說明】

flip: 紙牌的外框

down:表示向下翻牌動效,還有對于的up。后面章節會具體講解。

front: 表示位于前面的紙牌

back: 表示位于后面的紙牌

number*: 表示紙牌上的數字

flip的CSS代碼如下:

.flip {

display: inline-block;

position: relative;

width: 60px;

height: 100px;

line-height: 100px;

border: solid 1px #000;

border-radius: 10px;

background: #fff;

font-size: 66px;

color: #fff;

box-shadow: 0 0 6px rgba(0, 0, 0, .5);

text-align: center;

font-family: "Helvetica Neue"

}

復制代碼

這段代碼很基礎,就不再詳細解釋了。眼尖的同學可能發現了,為什么要設置background為#fff(白色)呢?最終效果明明是黑的。留個疑問,下一小節就會明白了。

基本結構的效果是這樣的:

1.2 構建紙牌并用偽元素拆分上下兩部分

由于每個紙牌是上下對折、翻轉的,所以每個紙牌要拆分成上下兩部分。可是HTML中每個紙牌只有一個標簽,怎么拆分成兩個呢?這里就用到了before和after偽元素。

知識點1: 偽元素的使用

先看代碼:

.flip .digital:before,

.flip .digital:after {

content: "";

position: absolute;

left: 0;

right: 0;

background: #000;

overflow: hidden;

}

.flip .digital:before {

top: 0;

bottom: 50%;

border-radius: 10px 10px 0 0;

}

.flip .digital:after {

top: 50%;

bottom: 0;

border-radius: 0 0 10px 10px;

}

復制代碼

:before和:after在digital內部生成了兩個偽元素,其中,before用來生成紙牌的“上半張”,after用來生成紙牌的“下半張”。

因此,before“上半張”為從“頂部(top: 0)”到“距底一半(bottom: 50%)”的部分,頂部兩側為圓角。

同理,after“下半張”為“距頂一半(top: 50%)”到“底部(bottom: 0)”的部分,底部兩側為圓角。

注意代碼中的content: ""不能省略,否則偽元素是不顯示的。

效果如下:

回答上一章節的問題,為什么底層設置background為白色?

答案很簡單,元素內部的紙片邊角和外層邊角之間會有一點點的縫隙,這個縫隙會露出底部的白色,從視覺效果上看,更加具有立體感。

然后,為上下部分中間添加一條水平折線。

.flip .digital:before,

.flip .digital:after {

content: "";

position: absolute;

left: 0;

right: 0;

background: #000;

overflow: hidden;

+ box-sizing: border-box;

}

...(略)

.flip .digital:before {

top: 0;

bottom: 50%;

border-radius: 10px 10px 0 0;

+ border-bottom: solid 1px #666;

}

復制代碼

外層flip添加box-sizing: border-box保證了下邊框不會影響元素的原有高度。

效果如下:

到這里,我們可以認為是4個小紙片,分別是:

前上:.digital.front:before

前下:.digital.front:after

后上:.digital.back:before

后下:.digital.back:after

由于重疊在一起,只能看到一張紙牌。而看到的這個紙牌是后面(back)的紙牌,為什么呢?因為back的HTML寫在了front的后面。不過沒關系,后面我們會通過z-index來重新調整層疊順序,先不著急。

1.3 為紙牌添加文字

還記的剛才的content: ""嗎?紙牌的文字顯示就用到了這個。

先通過CSS定義好0~9的數字:

.flip .number0:before,

.flip .number0:after {

content: "0";

}

.flip .number1:before,

.flip .number1:after {

content: "1";

}

.flip .number2:before,

.flip .number2:after {

content: "2";

}

...(略)

.flip .number9:before,

.flip .number9:after {

content: "9";

}

復制代碼

現在效果如下:

可以很明顯的看到兩個問題:

本應該在后面的back紙牌跑到了前面(z-index問題)

下半張紙牌的文字應該只顯示下半部分。

先來解決問題2,這里就涉及到了第二個知識點。

知識點2:line-height: 0的妙用

提到文字的顯示,肯定會想到基線(baseline),可能你也曾經看過這個圖:

關于基線(baseline)的計算,確實很麻煩,我也在這里繞了很久。其實理解line-height:0可以換個角度,會更容易理解,請看下圖:

當line-height為200px,每行文字高度為200px,文字在200px高度的行間區域垂直居中;

當line-height為100px,每行文字高度為100px,文字在100px高度的行間區域垂直居中;

當line-height為0時,行間距為0,中線的位置也為0,所以文字只有下半部分留在容器內。

利用line-height:0的特性,就可以很輕易實現“下半張”紙牌只顯示文字的下半部分,并且與“上半張”紙牌很好的銜接在一起。

在代碼中設置line-height為0:

.flip .digital:after {

top: 50%;

bottom: 0;

border-radius: 0 0 10px 10px;

+ line-height: 0;

}

復制代碼

效果如下:

1.4 設置紙牌的層疊關系

首先,先看下“向下翻牌”的視頻演示,直觀感受下每個紙片的層級關系:

按照實物圖就可以確定每張紙片的z-index:

添加以下CSS代碼:

/*向下翻*/

.flip.down .front:before {

z-index: 3;

}

.flip.down .back:after {

z-index: 2;

}

.flip.down .front:after,

.flip.down .back:before {

z-index: 1;

}

復制代碼

現在效果如下:

咦?怎么不對?別著急,這是因為我們只設置了層級,但是沒有把后面紙牌的下半部翻轉上去。

添加翻轉代碼:

.flip.down .back:after {

z-index: 2;

+ transform-origin: 50% 0%;

+ transform: perspective(160px) rotateX(180deg);

}

復制代碼

這里涉及到了知識點3。

知識點3:transform-origin和perspective

transform-origin是元素旋轉的基本點。

transform-origin: 50% 0%;表示將旋轉基本點設置在橫軸的中點,縱軸的頂點位置,如下圖所示:

perspective(160px)可以理解為立體透視圖的景深。在本次分享的效果中,我們的視角是正對牌面,并且紙牌位于視角中間。所以 transform-origin的第一個值(X軸位置)為50%。

rotateX(180deg)表示以X軸進行翻轉,對應這里就是上下翻轉。這里已經通過transform-origin的第二個參數(Y軸位置:0%)將X軸放在了元素頂部。

基于以上設置,已經可以正常顯示了,如下圖:

同理,“向上翻”也需要進行設置下。大家可以自己折兩個紙片,參照上面的方法,應該很容易實現。這里不再重復講解,直接放上代碼,大家可以對比下哪里不同:

/*向上翻*/

.flip.up .front:after {

z-index: 3;

}

.flip.up .back:before {

z-index: 2;

transform-origin: 50% 100%;

transform: perspective(160px) rotateX(-180deg);

}

.flip.up .front:before,

.flip.up .back:after {

z-index: 1;

}

復制代碼

2 翻牌動畫的實現

現在紙片都已擺好了,剩下的就是實現CSS3動畫,以及JS交互控制。

2.1 CSS3翻牌動畫

我們還是以“向下翻”為例,再來看下之前的實物翻牌視頻:

可以看到,“向下翻”主要涉及兩個元素的動畫:

前面紙牌的上半部向下翻轉180度。

后面紙牌的下半部(目前已翻轉上去)向下翻轉180度恢復原狀態。

直接上代碼:

.flip.down.go .front:before {

transform-origin: 50% 100%;

animation: frontFlipDown 0.6s ease-in-out both;

box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);

}

.flip.down.go .back:after {

animation: backFlipDown 0.6s ease-in-out both;

}

@keyframes frontFlipDown {

0% {

transform: perspective(160px) rotateX(0deg);

}

100% {

transform: perspective(160px) rotateX(-180deg);

}

}

@keyframes backFlipDown {

0% {

transform: perspective(160px) rotateX(180deg);

}

100% {

transform: perspective(160px) rotateX(0deg);

}

}

復制代碼

以上代碼涉及的知識點和原理沒有新的東西,都已經講解過了,就不詳述了。box-shadow是為了給紙片的上邊緣加一點白光,視覺效果更好一點。否則在翻轉的時候,跟后面元素都是黑色,融在一起了。看看現在的效果:

顯示不正常!為什么?因為前排上半部紙片的z-index最高,所以它在翻轉到下半部的時候仍然遮擋住了其他紙片。怎么優雅的解決?超級簡單,來看看第四個知識點:

知識點4:backface-visibility

backface-visibility表示元素的背面是否可見,默認為visible(可見)。

這里的需求是,當前面上半部紙片翻轉到一半的時候(90度)進入不可見狀態。而紙牌翻轉90度以后,正好是顯露元素背面的開始,所以將backface-visibility設置為hidden即可完美解決!

修改代碼如下:

.flip.down.go .front:before {

transform-origin: 50% 100%;

animation: frontFlipDown 0.6s ease-in-out both;

box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);

+ backface-visibility: hidden;

}

復制代碼

現在效果很完美!

大家可以試著自己實現向上翻轉效果,代碼直接放出:

.flip.up.go .front:after {

transform-origin: 50% 0;

animation: frontFlipUp 0.6s ease-in-out both;

box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3);

backface-visibility: hidden;

}

.flip.up.go .back:before {

animation: backFlipUp 0.6s ease-in-out both;

}

@keyframes frontFlipUp {

0% {

transform: perspective(160px) rotateX(0deg);

}

100% {

transform: perspective(160px) rotateX(180deg);

}

}

@keyframes backFlipUp {

0% {

transform: perspective(160px) rotateX(-180deg);

}

100% {

transform: perspective(160px) rotateX(0deg);

}

}

復制代碼

2.2 JS實現翻牌交互

現在我們來實現一個簡單的交互。需求是:

點擊“+”,向下翻牌,數字+1

點擊“-”,向上翻牌,數字-1

首先,修改下HTML:

+

M

+

+

+ 向下翻+1

+ 向上翻-1

+

復制代碼

配套的CSS如下,僅為了demo好看,無實際作用:

.single-demo {

margin: 50px auto;

padding: 30px;

width: 600px;

text-align: center;

border: solid 1px #999;

}

復制代碼

Javascript代碼如下:

var flip = document.getElementById('flip')

var backNode = document.querySelector('.back')

var frontNode = document.querySelector('.front')

var btn1 = document.getElementById('btn1')

var btn2 = document.getElementById('btn2')

btn1.addEventListener('click', function() {

flipDown();

})

btn2.addEventListener('click', function() {

flipUp();

})

// 當前數字

var count = 0

// 是否正在翻轉(防止翻轉未結束就進行下一次翻轉)

var isFlipping = false

// 向下翻轉+1

function flipDown() {

// 如果處于翻轉中,則不執行

if (isFlipping) {

return false

}

// 設置前牌的文字

frontNode.setAttribute('class', 'digital front number' + count)

// 計算后牌文字(越界判斷)

var nextCount = count >= 9 ? 0 : (count + 1)

// 設置后牌的文字

backNode.setAttribute('class', 'digital back number' + nextCount)

// 添加go,執行翻轉動畫

flip.setAttribute('class', 'flip down go')

// 將翻轉態設置為true

isFlipping = true

// 翻轉結束后,恢復狀態

setTimeout(function() {

// 去掉go

flip.setAttribute('class', 'flip down')

// 將翻轉態設置為false

isFlipping = false

// 設置前牌文字為+1后的數字

frontNode.setAttribute('class', 'digital front number' + nextCount)

// 更新當前文字

count = nextCount

}, 1000)

}

// 向上翻轉-1(同理,注釋略)

function flipUp() {

if (isFlipping) {

return false

}

frontNode.setAttribute('class', 'digital front number' + count)

var nextCount = count <= 0 ? 9 : (count - 1)

backNode.setAttribute('class', 'digital back number' + nextCount)

flip.setAttribute('class', 'flip up go')

isFlipping = true

setTimeout(function() {

flip.setAttribute('class', 'flip up')

isFlipping = false

frontNode.setAttribute('class', 'digital front number' + nextCount)

count = nextCount

}, 1000)

}

復制代碼

先看下交互效果:

這段Javascript代碼很冗余,重復代碼很多。在實際產品中,都是多個數字牌,這種方式顯然無法應對。下一章節,我們來說下如何優雅的封裝,以不變應萬變。

3 翻牌時鐘的實現

先看下最終效果:

3.1 HTML構建

HTML代碼如下:

:

:

復制代碼

CSS代碼如下(之前章節的CSS代碼請保留):

.clock {

text-align: center;

}

.clock em {

display: inline-block;

line-height: 102px;

font-size: 66px;

font-style: normal;

vertical-align: top;

}

復制代碼

效果如下,剩下的就是JS部分了。

3.2 構建Flipper類

將每個翻牌封裝成類,這樣在應對多個翻牌的時候,可以方便的通過new Flipper()去獨立控制每個翻牌對象。

類的實現代碼如下:

function Flipper(config) {

// 默認配置

this.config = {

// 時鐘模塊的節點

node: null,

// 初始前牌文字

frontText: 'number0',

// 初始后牌文字

backText: 'number1',

// 翻轉動畫時間(毫秒,與翻轉動畫CSS 設置的animation-duration時間要一致)

duration: 600,

}

// 節點的原本class,與html對應,方便后面添加/刪除新的class

this.nodeClass = {

flip: 'flip',

front: 'digital front',

back: 'digital back'

}

// 覆蓋默認配置

Object.assign(this.config, config)

// 定位前后兩個牌的DOM節點

this.frontNode = this.config.node.querySelector('.front')

this.backNode = this.config.node.querySelector('.back')

// 是否處于翻牌動畫過程中(防止動畫未完成就進入下一次翻牌)

this.isFlipping = false

// 初始化

this._init()

}

Flipper.prototype = {

constructor: Flipper,

// 初始化

_init: function() {

// 設置初始牌面字符

this._setFront(this.config.frontText)

this._setBack(this.config.backText)

},

// 設置前牌文字

_setFront: function(className) {

this.frontNode.setAttribute('class', this.nodeClass.front + ' ' + className)

},

// 設置后牌文字

_setBack: function(className) {

this.backNode.setAttribute('class', this.nodeClass.back + ' ' + className)

},

_flip: function(type, front, back) {

// 如果處于翻轉中,則不執行

if (this.isFlipping) {

return false

}

// 設置翻轉狀態為true

this.isFlipping = true

// 設置前牌文字

this._setFront(front)

// 設置后牌文字

this._setBack(back)

// 根據傳遞過來的type設置翻轉方向

let flipClass = this.nodeClass.flip;

if (type === 'down') {

flipClass += ' down'

} else {

flipClass += ' up'

}

// 添加翻轉方向和執行動畫的class,執行翻轉動畫

this.config.node.setAttribute('class', flipClass + ' go')

// 根據設置的動畫時間,在動畫結束后,還原class并更新前牌文字

setTimeout(() => {

// 還原class

this.config.node.setAttribute('class', flipClass)

// 設置翻轉狀態為false

this.isFlipping = false

// 將前牌文字設置為當前新的數字,后牌因為被前牌擋住了,就不用設置了。

this._setFront(back)

}, this.config.duration)

},

// 下翻牌

flipDown: function(front, back) {

this._flip('down', front, back)

},

// 上翻牌

flipUp: function(front, back) {

this._flip('up', front, back)

}

}

復制代碼

可以注意到,Flipper的傳參只接受一個對象形式的參數config,使用對象的方式向函數傳參有很多優點:

參數語義化,方便理解

不用在意參數順序

傳參的增刪和順序調整不會影響業務代碼的使用

使用Object.assign方法,可將傳遞進來的config參數覆蓋默認參數。傳遞的config中沒有的屬性,則使用默認配置。當然,這種方式只適用于淺拷貝。

關于prototype,以及為什么要設置constructor,請閱讀我的另一篇文章《一張刮刮卡竟包含這么多前端知識點》第4.1章節,已經講解得很詳細了。

代碼邏輯請閱讀注釋。

3.3 實現時鐘業務邏輯

接下來的工作就是將js與dom進行綁定。

請看代碼:

這段代碼一定要放在Flipper類代碼的下面,Flipper.prototype一定要在業務邏輯代碼之前執行,否則會報錯找不到Flipper內部方法。

// 定位時鐘模塊

let clock = document.getElementById('clock')

// 定位6個翻板

let flips = clock.querySelectorAll('.flip')

// 獲取當前時間

let now = new Date()

// 格式化當前時間,例如現在是20:30:10,則輸出"203010"字符串

let nowTimeStr = formatDate(now, 'hhiiss')

// 格式化下一秒的時間

let nextTimeStr = formatDate(new Date(now.getTime() + 1000), 'hhiiss')

// 定義牌板數組,用來存儲6個Flipper翻板對象

let flipObjs = []

for (let i = 0; i < flips.length; i++) {

// 創建6個Flipper實例,初始化并存入flipObjs

flipObjs.push(new Flipper({

// 每個Flipper實例按數組順序與翻板DOM的順序一一對應

node: flips[i],

// 按數組順序取時間字符串對應位置的數字

frontText: 'number' + nowTimeStr[i],

backText: 'number' + nextTimeStr[i]

}))

}

復制代碼

代碼邏輯不難,請閱讀注釋。比較值得分享的是其中的時間格式化函數formatDate。

知識點5:時間格式化函數的實現

為了方便業務使用,實現一個時間格式化方法,這個方法在很多其他業務中都會使用到,具有很普遍的實用價值。

需求是通過輸入日期時間格式要求,輸出對應的字符串。

例如:

yyyy-mm-dd hh:ii:ss 輸出:2019-06-02 08:30:37

yy-m-d h:i:s 輸出:19-6-2 8:30:37

先看代碼:

//正則格式化日期

function formatDate(date, dateFormat) {

/* 單獨格式化年份,根據y的字符數量輸出年份

* 例如:yyyy => 2019

yy => 19

y => 9

*/

if (/(y+)/.test(dateFormat)) {

dateFormat = dateFormat.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));

}

// 格式化月、日、時、分、秒

let o = {

'm+': date.getMonth() + 1,

'd+': date.getDate(),

'h+': date.getHours(),

'i+': date.getMinutes(),

's+': date.getSeconds()

};

for (let k in o) {

if (new RegExp(`(${k})`).test(dateFormat)) {

// 取出對應的值

let str = o[k] + '';

/* 根據設置的格式,輸出對應的字符

* 例如: 早上8時,hh => 08,h => 8

* 但是,當數字>=10時,無論格式為一位還是多位,不做截取,這是與年份格式化不一致的地方

* 例如: 下午15時,hh => 15, h => 15

*/

dateFormat = dateFormat.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));

}

}

return dateFormat;

};

//日期時間補零

function padLeftZero(str) {

return ('00' + str).substr(str.length);

}

復制代碼

代碼邏輯請閱讀注釋,這里再補充下“日期時間補零padLeftZero”函數的說明。由于月、日、時、分、秒最多為2位數,所以這里只考慮最多補一個0的情況。

原理是:不管數字是幾位,先在前面補兩個0,再根據原數字的位數進行截取,最終輸出固定為兩位的補零數字

例如:數字"16"是兩位數,先補兩個0變成"0016",再從該字符串的索引[2]開始截取(2=原數字的位數),由于字符串索引從[0]開始,所以[2]對應字符串的第3位,輸出結果仍為"16。

同理,數字"8"是1位數,先補兩個0變成"008",再從該字符串的索引[1]開始截取(1=原數字的位數),即從第2位開始截取,輸出"08"。

這樣就實現了補零的功能。

現在看下效果,已經可以正確顯示當前時間了。

3.4 運行時鐘

萬事俱備,只差加個定時器讓時鐘翻動起來。

setInterval(function() {

// 獲取當前時間

let now = new Date()

// 格式化當前時間

let nowTimeStr = formatDate(new Date(now.getTime() - 1000), 'hhiiss')

// 格式化下一秒時間

let nextTimeStr = formatDate(now, 'hhiiss')

// 將當前時間和下一秒時間逐位對比

for (let i = 0; i < flipObjs.length; i++) {

// 如果前后數字沒有變化,則直接跳過,不翻牌

if (nowTimeStr[i] === nextTimeStr[i]) {

continue

}

// 傳遞前后牌的數字,進行向下翻牌動畫

flipObjs[i].flipDown('number' + nowTimeStr[i], 'number' + nextTimeStr[i])

}

}, 1000)

復制代碼

這段代碼邏輯很簡單了,主要就是進行前后時間字符串的對比,然后設置紙牌并翻轉。最終效果:

4 Vue & React封裝

由于篇幅有限,這里不再詳述,原理都是一樣的,只是利用Vue和React的API和語法進行封裝。

原生JavaScript、Vue、React三個版本的演示源碼請到我的github下載:

本次分享講解了如何優雅地實現結構簡單的翻牌時鐘,并對JS進行了科學高效的封裝。其中也涉及到了CSS3的一些知識點和技巧。希望能對大家的工作有所幫助。

歡迎關注我的個人微信公眾號,隨時獲取最新文章^_^

總結

以上是生活随笔為你收集整理的html时钟翻牌效果,干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。