Double 11 chop hands festival in the past, we should see the effect of digital flip in many pages, such as countdown, digital growth, etc.. I believe that many people have implemented their own independent, I also saw some demo on the Internet, found that the HTML structure is mostly complex, with 4 parallel tags to place the two “cards” before and after. This article will explain how to further simplify HTML, so that the structure is simple, so that THE JS method encapsulated easy to use. Here’s the final result:

HTML structure for each flip (reduced to 2 side tags) :

<div class="flip down">
    <div class="digital front number0"></div>
    <div class="digital back number1"></div>
</div>
Copy the code

There are many tips that can be used flexibly to improve technical skills and work efficiency. These tips include:

The use of false elements

The height of the line is 0

Transform-origin and Perspective

4. Backface – Visibility

Knowledge 5: The implementation of the time formatting function

Let’s do it!

1. Construction of flop

1.1 Basic Structure

First, explain the structure of HTML:

<! --> <div class="flip down"> <! -- the card at the front --> <div class="digital front number0"></div> <! -- the card behind --> <div class="digital back number1"></div>
</div>
Copy the code

【 description 】

Flip: The outside of a playing card

Down: indicates the down flip card dynamic effect, and for up. This will be explained in later chapters.

Front: indicates the card in front

Back: indicates the card at the back

Number *: indicates the number on the card

The CSS code for flip is as follows:

.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"
}
Copy the code

This code is very basic and won’t be explained in detail. For eagle-eyed students, why set background to # FFF (white)? The end result is clearly black. Leave a question and the next section will make sense.

The basic structure works like this:

1.2 Build a card and split the upper and lower parts with pseudo-elements

Since each card is folded and flipped upside down, each card is split into two halves. Each card in HTML has only one tag, so how do you split it into two? This is where the before and after pseudo-elements are used.

Point 1: The use of pseudo-elements

First look at the code:

.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;
}
Copy the code

:before and :after generate two pseudo-elements inside digital, where before is used to generate the “top half” of the card and after is used to generate the “bottom half” of the card.

Therefore, before “top half” is the part from “top (0)” to “bottom (50%)”, with rounded corners on both sides of the top.

In the same way, after “bottom half” is the part between “top: 50%” and “bottom: 0”, with rounded corners on both sides.

Note that content: “” cannot be omitted in the code, otherwise the pseudo-elements will not be displayed.

The effect is as follows:

To answer the question in the previous chapter, why is the background set to white?

The answer is very simple. There will be a little gap between the inner edge of the element and the outer edge of the element. This gap will reveal the white at the bottom, which is more three-dimensional from the visual effect.

Then, add a horizontal crease line in the middle of the upper and lower sections.

    .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;
    }
Copy the code

Box-sizing: border-box ensures that the bottom border does not affect the height of the element.

The effect is as follows:

Here, we can think of four pieces of paper, which are:

  1. On the former: digital. Front: before
  2. Under the former: digital. Front: after
  3. After:. Digital. Back: before
  4. After next: digital. Back: after

Because of the overlap, only one card can be seen. And this card you see is the back card. Why? Because the HTML of the back is written after the front. But it doesn’t matter, we will use z-index to rearrange the cascade order later, so don’t worry.

1.3 Add text to cards

Remember the content: “”? This is used for the text display of cards.

Define numbers from 0 to 9 in the CSS.

.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";
}
Copy the code

Now it looks like this:

Two problems can be clearly seen:

  1. The back card that should have been behind went to the front (Z-index problem)
  2. The text on the bottom half of the card should only show the bottom half.

Let’s start with problem number two, which brings us to the second point.

The height of the line is 0

When it comes to text display, baseline comes to mind, and you’ve probably seen this one:

The calculation of baseline is really troublesome, and I’ve been doing a lot of detour here. [height:0] [height:0]

When line-height is 200px, each line is 200px high and the text is vertically centered at 200px height.

When line-height is 100px, each line should be 100px high, and the text should be vertically centered at 100px between lines.

When line-height is 0, the line spacing is 0 and the center line position is 0, so only the bottom half of the text remains in the container.

Using the line-height:0 feature, it is easy to make the “bottom” card show only the bottom half of the text, and fit nicely into the “top” card.

Set line-height to 0 in the code:

    .flip .digital:after {
        top: 50%;
        bottom: 0;
        border-radius: 0 0 10px 10px;
+       line-height: 0;
    }
Copy the code

The effect is as follows:

1.4 Set the cascading relationship of cards

First of all, watch the video demonstration of “Turn down” to intuitively feel the hierarchical relationship of each piece of paper:

The z-index of each piece of paper can be determined according to the physical picture:

Add the following CSS code:

/* down */.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; }Copy the code

Now it looks like this:

Yi? What’s wrong? Don’t worry, this is because we only set the hierarchy, but we didn’t flip the bottom half of the following card up.

Add rollover code:

    .flip.down .back:after {
        z-index: 2;
+       transform-origin: 50% 0%;
+       transform: perspective(160px) rotateX(180deg);
    }
Copy the code

This is point 3.

Transform-origin and Perspective

Transform-origin is the basic point at which an element is rotated.

transform-origin: 50% 0%; Indicates that the rotation base point is set at the midpoint of the horizontal axis and the vertex of the vertical axis, as shown in the figure below:

Perspective (160px) can be understood as the depth of field of a stereo view. In this shared effect, our view is facing the card, and the card is in the middle of the view. So the first value of transform-origin (x-position) is 50%.

RotateX (180deg) means you flip it on the X-axis, so you flip it up and down. The X-axis has been placed at the top of the element by the transform-Origin second parameter (y-position: 0%).

Based on the above Settings, it can be displayed normally, as shown below:

Similarly, “flip up” also needs to be set. You can fold two pieces of paper yourself, and it should be very easy to do that. Instead of repeating the explanation, here is the code, and you can compare what is different:

/ /.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; }Copy the code

2 flip animation implementation

Now that the pieces of paper are in place, all that remains is to implement CSS3 animation and JS interactive control.

2.1 CSS3 flip animation

Let’s take “flip down” as an example, and watch the previous video of the physical flip:

As you can see, “scroll down” mainly involves two elements of animation:

  1. The top half of the front card flips down 180 degrees.
  2. The lower half of the back card (now flipped up) is flipped down 180 degrees to restore its original state.

Directly on the code:

.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); }}Copy the code

There is nothing new about the knowledge points and principles involved in the above code, which have been explained and will not be detailed. Box-shadow is to add a bit of white light to the upper edge of the paper for a better visual effect. Otherwise, when you flip it, it’s all black with the elements behind it, and it melts together. See how it looks now:

Display abnormal! Why is that? Because the top half of the paper in the front row has the highest Z-index, it still hides other pieces of paper when it flips to the bottom half. How to do it gracefully? Super easy. Let’s look at number four:

4. Backface – Visibility

Backface -visibility indicates whether the back of the element is visible. The default is visible.

The requirement here is that the piece of paper on the front surface becomes invisible when it is flipped halfway (90 degrees). After the card is flipped 90 degrees, it will be the beginning of the exposure to the back of the element, so set backface-visibility to Hidden!

Modify the code as follows:

.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; }Copy the code

Now it’s perfect!

You can try to implement the flip up effect yourself, and the code is directly released:

.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); }}Copy the code

2.2 JS to realize card flip interaction

Now let’s implement a simple interaction. Demand is:

  1. Click on the “+”, flip down, the number +1
  2. Click “-” to flip the card up to the number -1

First, modify the HTML:

+   <div class="single-demo">
M       <div class="flip down" id="flip">
            <div class="digital front number0"></div>
	        <div class="digital back number1"></div>
	    </div>
+	</div>
+   <div class="btn-con">
+       <button id="btn1"</button> + <button id="btn2"</button> + </div>Copy the code

The CSS is provided as follows. The CSS is only useful for demo:

.single-demo {
    margin: 50px auto;
    padding: 30px;
    width: 600px;
    text-align: center;
    border: solid 1px # 999;
}
Copy the code

The Javascript code is as follows:

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(); }) // If flipping is happening (to prevent the next flipping) var isFlipping =false// Flip down +1function flipDown() {// Do not execute if you are in a rolloverif (isFlipping) {
        return false} // Set the front card text frontnode.setattribute ('class'.'digital front number'Var nextCount = count >= 9? 0: (count + 1) // Set the following card text backnode.setattribute ('class'.'digital back number'+ nextCount) // Add go, execute flip animation flip. SetAttribute ('class'.'flip down go'// Set the flip state totrue
    isFlipping = true// After the rollover is complete, the state is restoredsetTimeout(function() {// go flip. SetAttribute ()'class'.'flip down'// Set the flip state tofalse
        isFlipping = falseFrontnode.setattribute (frontNode.setAttribute();'class'.'digital front number'NextCount = nextCount, 1000)}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)
}
Copy the code

Let’s take a look at the interaction:

This Javascript code is very redundant and has a lot of duplicate code. In the actual product, is a number of word cards, this way obviously can not deal with. In the next chapter, we’ll look at how to encapsulate elegantly, without changing.

3. Realization of flip clock

Take a look at the final result:

3.1 HTML to build

The HTML code is as follows:

<div class="clock" id="clock">
    <div class="flip down">
        <div class="digital front number0"></div>
        <div class="digital back number1"></div>
    </div>
    <div class="flip down">
        <div class="digital front number0"></div>
        <div class="digital back number1"></div>
    </div>
    <em>:</em>
    <div class="flip down">
        <div class="digital front number0"></div>
        <div class="digital back number1"></div>
    </div>
    <div class="flip down">
        <div class="digital front number0"></div>
        <div class="digital back number1"></div>
    </div>
    <em>:</em>
    <div class="flip down">
        <div class="digital front number0"></div>
        <div class="digital back number1"></div>
    </div>
    <div class="flip down">
        <div class="digital front number0"></div>
        <div class="digital back number1"></div>
    </div>
</div>
Copy the code

The CSS code is as follows (please keep the CSS code in previous chapters):

.clock {
    text-align: center;
}

.clock em {
    display: inline-block;
    line-height: 102px;
    font-size: 66px;
    font-style: normal;
    vertical-align: top;
}
Copy the code

The effect is as follows, and the rest is the JS part.

3.2 Building the Flipper class

Each flip card is encapsulated into a class, so that when dealing with multiple flips, each flip object can be easily controlled independently through new Flipper().

Class implementation code is as follows:

functionFlipper(config) {// default config this.config = {// clock module node: null, // initial prefix text frontText:'number0'// backText:'number1', // Flip animation time (milliseconds, same as animation CSS set animation-duration) duration: } // add/remove new class this.nodeClass = {flip:'flip',
        front: 'digital front',
        back: 'digital back'} / / override the default configuration Object. The assign (this config, config) / / location before and after the two CARDS of the DOM node enclosing frontNode = this. Config. Node. QuerySelector ('.front')
    this.backNode = this.config.node.querySelector('.back') // If an animation is in the process of flipping (to prevent the next flipping) this.isFlipping =falsePrototype = {constructor: constructor, // initialize _init:function() {// set the initial card character this._setfront (this.config.fronttext) this._setBack(this.config.backtext)}, // set the initial card text _setFront:function(className) {
        this.frontNode.setAttribute('class', this.nodeClass.front + ' '+ className)}, // set the backcard text _class:function(className) {
        this.backNode.setAttribute('class', this.nodeClass.back + ' ' + className)
    },
    _flip: function(type, front, back) {// If it is in a rollover, it will not be executedif (this.isFlipping) {
            return false} // Set the rollover state totrue
        this.isFlipping = true// Set the front text this._setFront(front) // Set the rear text this. _back (back) // based on what is passedtypeSet the flip Directionlet flipClass = this.nodeClass.flip;
        if (type= = ='down') {
            flipClass += ' down'
        } else {
            flipClass += ' up'} / / add reverse direction and perform the animation class, perform flips animation this. Config. Node. SetAttribute ('class', flipClass + ' go') // According to the set animation time, at the end of the animation, restore the class and update the previous card textsetTimeout (() = > {/ / reduction class this. Config. Node. The setAttribute ('class', flipClass) // Set the flip state tofalse
            this.isFlipping = false// Set the previous card text to the current new number, the back card is blocked by the previous card, do not need to set. This._setfront (back)}, this.config.duration)}, // flipDown:function(front, back) {
        this._flip('down', front, back)}, // flips up:function(front, back) {
        this._flip('up', front, back)
    }
}
Copy the code

Note that Flipper only takes an object parameter, config, which has many advantages:

  1. Parameter semantics, easy to understand
  2. Don’t worry about the order of arguments
  3. The addition, deletion, and order adjustment of parameters do not affect the use of service codes

Assign overrides the default config parameters passed in using the object. assign method. Properties not found in the passed config, the default configuration is used. Of course, this approach only works for shallow copies.

For more on Prototype and why you should set Constructor, see section 4.1 of my article “Scratch Card Contains So much front-end Knowledge” for more details.

Read the comments for the code logic.

3.3 Implementing the Clock Service Logic

The next step is to bind JS to the DOM.

Look at the code:

Flipper. Prototype must be executed before the business logic code, otherwise an error will be reported that the Flipper internal method cannot be found.

// Locate the clock modulelet clock = document.getElementById('clock') // Position 6 flip plateslet flips = clock.querySelectorAll('.flip') // Get the current timeletNow = new Date() // Format the current time, such as 20:30:10, output"203010"stringlet nowTimeStr = formatDate(now, 'hhiiss') // Format the time for the next secondlet nextTimeStr = formatDate(new Date(now.getTime() + 1000), 'hhiiss') // Define a board array to store 6 Flipper flip objectslet flipObjs = []
for (leti = 0; i < flips.length; I++) {// create 6 Flipper instances, initialize and store flipObjs flipobjs. push(new Flipper({// each Flipper instance corresponds to node in array order and Flipper DOM order: Flips [I], // Take the number frontText corresponding to the time string in array order:'number' + nowTimeStr[i],
        backText: 'number' + nextTimeStr[i]
    }))
}
Copy the code

The code logic is not difficult, read the comments. Worth sharing is the time formatting function formatDate.

Knowledge 5: The implementation of the time formatting function

In order to facilitate business use, implement a time formatting method, this method will be used in many other businesses, has a very general practical value.

The requirement is to output the corresponding string by entering the date and time format requirements.

Such as:

Yyyy-mm-dd hh:ii:ss

Yy – M-D H: I: S Output: 19-6-2 8:30:37

First look at the code:

// Reformat the datefunctionFormatDate (date, dateFormat) {/* Format the year separately. Output the year based on the number of y characters. For example, YYYY => 2019 YY => 19 Y => 9 */if (/(y+)/.test(dateFormat)) {
        dateFormat = dateFormat.replace(RegExp.The $1, (date.getFullYear() + ' ').substr(4 - RegExp.The $1.length)); } // Format month, day, hour, minute, secondlet 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)) {// Fetch the corresponding valuelet str = o[k] + ' '; For example, hh => 08, h => 8 * When hh => 08, h => 8 * When hh => 08, h => 8 * When hh => 10, no truncation is done. This is inconsistent with the format of the year. At 15:00, hh => 15, h => 15 */ dateFormat = dateformat.replace (RegExp).The $1, (RegExp.The $1.length === 1) ? str : padLeftZero(str)); }}returndateFormat; }; // Date and time add zerofunction padLeftZero(str) {
    return ('00' + str).substr(str.length);
}
Copy the code

The code logic please read the comments, here to add “date and time zero padLeftZero” function description. Since month, day, hour, minute, and second are at most 2-digit numbers, we only consider the case where at most one 0 is added.

The principle is: no matter how many digits a number is, two zeros are added in front of it first, and then intercepted according to the number of digits of the original number. Finally, the output is fixed as two digits of zeros

For example, if the number “16” is a two-digit number, add two zeros to make “0016”, and then intercept from the index [2] of the string (2= the number of original digits). Since the string index starts from [0], [2] corresponds to the third digit of the string, and the output is still “16”.

Similarly, the number “8” is a 1-digit number. Add two zeros to make “008”, and then start with the index [1] of the string (1= the number of original digits), that is, start with the second digit, and output “08”.

In this way, the function of filling zero is realized.

Now if you look at the effect, you can display the current time correctly.

3.4 Running Clock

All we need is a timer to turn the clock.

setInterval(function() {// Get the current timeletNow = new Date() // Format current timelet nowTimeStr = formatDate(new Date(now.getTime() - 1000), 'hhiiss') // Format the next second timelet nextTimeStr = formatDate(now, 'hhiiss') // Compare the current time bit by bit with the next second timefor (leti = 0; i < flipObjs.length; I++) {// if there is no change in the number before and after the card, the card is skippedif (nowTimeStr[i] === nextTimeStr[i]) {
            continue} // flipObjs[I].flip down ('number' + nowTimeStr[i], 'number' + nextTimeStr[i])
    }
}, 1000)
Copy the code

This code logic is very simple, the main is to compare before and after the time string, and then set the card and flip. End result:

4 Vue & React encapsulation

Due to limited space, I won’t go into details here. The principle is the same, but the API and syntax of Vue and React are used for encapsulation.

JavaScript, Vue and React can be downloaded from Github.

Github.com/Yuezi32/fli…

This share explains how to elegantly implement a simple flip clock structure, and the scientific and efficient encapsulation of JS. It also involves some knowledge points and skills of CSS3. I hope I can help you with your work.

Please follow my personal wechat official number to get the latest articles ^_^