How is the crazy praise animation of H5 live broadcast achieved? (With complete source code)

There is a very important interaction in live broadcasting: like it.

In order to highlight the atmosphere of the live broadcast room, live broadcasts usually have two special needs compared to ordinary video or text content:

  • Like action unlimited times, guide users to like crazy
  • All the crazy likes in the live broadcast room need to be animated in all user interfaces

Let's first look at the renderings:

 

 

We also see some important information from the renderings:

  • The likes of the animation images vary in size, and the motion track is also random
  • Like animation pictures are zoomed in first and then move at a constant speed.
  • As we approached the top, it gradually disappeared.
  • When a large number of like requests are received, the like animation does not get together, and it continues to appear in order.

So how to achieve these requirements? Here are two ways to achieve it (complete demo at the bottom):

CSS3 implementation

Using CSS3 to achieve animation, obviously, we think of animation.

First, let's take a look at the combination of animation, the specific meaning will not be explained, if you need it, you can understand it yourself.

animation: name duration timing-function delay iteration-count direction fill-mode play-state; 复制代码

We started to realize it step by step.

Step 1: Fix the area and set the basic style

First of all, we first prepare a picture of like animation:

image1

 

Take a look at the HTML structure. An outer structure fixes the position of the entire display animation area. Here is a div with a width of 100px and a height of 200px.

<div class="praise_bubble">
  <div class="bubble b1 bl1"></div>
</div>

.praise_bubble{ width:100px; height:200px; position:relative; background-color:#f4f4f4; } .bubble{ position: absolute; left:50%; bottom:0; }

 

 

image1

 

Step 2: exercise

Use animation frame animation to define a bubble_y frame sequence.

.bl1 {
    animation:bubble_y 4s linear 1 forwards ; 
 }
@keyframes bubble_y {
    0% {
        margin-bottom:0;
    }
    100% {
        margin-bottom:200px;
    }
}
 

Set the running time here 4s;
use linear motion linear, of course, you can also use other curves if you need it, such as ease;
each like animation only runs once; the
animation is only forwards.

 

image1

 

Step 3: add fade

Dissolve effect, just use opacity. Here we fixed to fade in the last quarter. Modify bubble_y:

@keyframes bubble_y {
    0% {
        margin-bottom:0;
    }
    75%{
        opacity:1;
    }
    100% {
        margin-bottom:200px;
        opacity:0;
    }
}

 

image1

 

Step 4: Increase animation zoom effect

At the beginning of a short period of time, the picture changes from small to large.

So we add an animation: bubble_big_1.

Here, the original image is enlarged from 0.3 times to 1 times. Pay attention to the running time here, such as the above setting, a total of 4s from the beginning to the end of the animation, then this amplification time can be set as needed, such as 0.5s.

.bl1 {
    animation:bubble_big 0.5s linear 1 forwards; 
 }
@keyframes bubble_big_1 {
    0% {
        transform: scale(.3);
    }
    100% {
        transform: scale(1);
    }
  }

 

 

Step 5: Set the offset

We first define the frame animation: bubble_1 to perform the offset. The picture begins to zoom in, and no offset is set here, keeping the middle origin unchanged.

After running to 25% * 4 = 1s, that is, after 1s, when it is offset to the left by -8px, 2s, when it is offset by 8px to the right, when it is offset by 3s, it is offset by 15px, and finally it is offset by 15px to the right.

As you can think of, this is a classic left and right swing trajectory, "left to right to left to right" curve swing effect.

@keyframes bubble_1 {
    0% {
    }
    25% {
        margin-left:-8px;
    }
    50% {
        margin-left:8px
    }
    75% {
        margin-left:-15px
    }
    100% {
        margin-left:15px
    }
}
 

The effect chart is as follows:

 

image1

 

Step 6: Fill in the animation style

Here is preset a running curve trajectory, left and right swing style, we are presetting more kinds of curves to achieve the purpose of random trajectory.

For example, the left and right offset animation track of bubble_1, we can modify the offset value to achieve different curve tracks.

Step 7: JS operation randomly increases node style

Provide a way to add likes, randomly combine the styles of likes, and then render to the node.

let praiseBubble = document.getElementById("praise_bubble");
let last = 0;
function addPraise() {
    const b =Math.floor(Math.random() * 6) + 1;
    const bl =Math.floor(Math.random() * 11) + 1; // bl1~bl11
    let d = document.createElement("div");
    d.className = `bubble b${b} bl${bl}`;
    d.dataset.t = String(Date.now());
    praiseBubble.appendChild(d);
}
setInterval(() => {
    addPraise ();
},300)
 

When using CSS to achieve likes, you usually also need to pay attention to setting the random delay of bubble, such as:

.bl2 {
    animation:bubble_2 $bubble_time linear .4s 1 forwards,bubble_big_2 $bubble_scale linear .4s 1 forwards,bubble_y $bubble_time linear .4s 1 forwards;   
}
 

Here if it is random to bl2, then run after delaying 0.4s, bl3 delays 0.6s ...

If it is a batch update to the node, without setting a delay, it will get together. Random “bl” style, random delay, and then appear in batches, will automatically display staggered peaks. Of course, we also need to increase the animation that the current user manually likes, this does not require a delay.

In addition, there may be 40 likes issued by others at the same time. The business requirement is usually to hope that these 40 like bubbles will appear one after another, creating a continuous like atmosphere, otherwise the large amount of releases will be displayed together.

Then we also need to break up the number of likes in batches. For example, the time for a like ($ bubble_time) is 4s. So in 4s, how many likes will appear at the same time? For example, 10, then 40 likes, you need to render 4 times in batches.

 window.requestAnimationFrame(() => {
     // Continue to process the batch
     render();
 });
 

In addition, you need to manually clear the node. To prevent performance problems caused by too many nodes. The following is a complete rendering.

 

 

Canvas drawing implementation

This is easy to understand, just draw the animation directly on the canvas. If you don't know the canvas, you can learn it later.

Step 1: Initialize

Create a new canvas tag on the page element to initialize the canvas.

The width and height properties can be set on the canvas, and the width and height can also be set in the style property.

  • The width and height of the style on the canvas are the height and width of the canvas rendered in the browser, that is, the actual width and height in the page.
  • The width and height of the canvas tag are the actual width and height of the canvas.
<canvas id="thumsCanvas" width="200" height="400" style="width:100px;height:200px"></canvas>
 

A canvas with a width of 200 and a height of 400 on the page, and then the entire canvas is displayed in the area of ​​100 width and 200 height. canvas The content of the canvas is doubled and displayed on the page.

Define a thumbs-up class, ThumbsUpAni. The constructor is to read the canvas and save the width and height values.

class ThumbsUpAni{
    constructor(){
        const canvas = document.getElementById('thumsCanvas');
        this.context = canvas.getContext('2d')!;
        this.width = canvas.width;
        this.height = canvas.height;
    }
}

Step 2: Load image resources in advance

Like pictures that need to be rendered randomly, preload first to obtain the width and height of the picture. If there is a download failure, the random picture will not be displayed. Nothing to say, simple and easy to understand.

loadImages(){
    const images = [
        'jfs/t1/93992/8/9049/4680/5e0aea04Ec9dd2be8/608efd890fd61486.png',
        'jfs/t1/108305/14/2849/4908/5e0aea04Efb54912c/bfa59f27e654e29c.png',
        'jfs/t1/98805/29/8975/5106/5e0aea05Ed970e2b4/98803f8ad07147b9.png',
        'jfs/t1/94291/26/9105/4344/5e0aea05Ed64b9187/5165fdf5621d5bbf.png',
        'jfs/t1/102753/34/8504/5522/5e0aea05E0b9ef0b4/74a73178e31bd021.png',
        'jfs/t1/102954/26/9241/5069/5e0aea05E7dde8bda/720fcec8bc5be9d4.png'
    ];
    const promiseAll = [] as Array<Promise<any>>;
    images.forEach((src) => {
        const p = new Promise(function (resolve) {
            const img = new Image;
            img.onerror = img.onload = resolve.bind(null, img);
            img.src = 'https://img12.360buyimg.com/img/' + src;
        });
        promiseAll.push(p);
    });
    Promise.all(promiseAll).then((imgsList) => {
        this.imgsList = imgsList.filter((d) => {
            if (d && d.width > 0) return true;
            return false;
        });
        if (this.imgsList.length == 0) {
            logger.error('imgsList load all error');
            return;
        }
    })
}
 

Step 2: Create rendering objects

Render the picture in real time, making it a coherent animation, it is important: to generate a curved trajectory. This curve trajectory needs to be a smooth uniform curve. If the generated curve trajectory is not smooth, the effect will be too abrupt, for example, the previous one is 10 px, the next one is -10px, then obviously, the animation is flickering left and right.

The ideal trajectory is the previous position is 10px, then 9px, and then smoothed to -10px, such coordinate points are coherent, it seems that the animation is running smoothly.

Randomly smooth X-axis offset

If you want to make a smooth curve, you can actually use the sine (Math.sin) function we are familiar with to achieve a uniform curve.

Look at the sine curve in the figure below:

 

image1

 

This is a graph from Math.sin (0) to Math.sin (9), it is a smooth curve from positive to negative, and then from negative to positive, which fully meets our needs, so We then need to generate a random ratio value to make the swing amplitude random.

const angle = getRandom (2, 10);
let ratio = getRandom (10.30) * ((getRandom (0, 1)? 1: -1));
const getTranslateX = (diffTime) => {
    if (diffTime < this .scaleTime) {// During zooming, no wobble offset
        return basicX;
    } else {
        return basicX + ratio*Math.sin(angle*(diffTime - this.scaleTime));
    }
};
 

scaleTime is how long it takes to zoom in from the beginning to the final size. Here we set 0.1, which is 10% of the time before the total running time. Like the picture and zoom in gradually.

diffTime is the percentage of time that has passed from the start of the animation to the current time. The actual value is gradually increased from 0-> 1. diffTime-scaleTime = 0 ~ 0.9, when diffTime is 0.4, it means that it has been running for 40% of the time.

Because the curve from Math.sin (0) to Math.sin (0.9) is almost a straight line, it is not very consistent with the swing effect. There is a slight change from Math.sin (0) to Math.sin (1.8), so here we are Set the minimum value of angle to 2.

This sets the angle factor angle to a maximum of 10, and runs two peaks from the bottom to the top.

Of course, if the running distance is longer, we can increase the angle value, for example, become 3 peaks (if the time is short, there are three peaks, it will run too fast, there is a flicker phenomenon). As shown below:

 

image1

 

Y axis offset

This is easy to understand, the diffTime is 0 at the beginning, so the running offset is from this.height-> image.height / 2. That is, from the bottom, running to the top to stay, in fact we will dilute and hide at the top.

const getTranslateY = (diffTime) => {
    return image.height / 2 + (this.height - image.height / 2) * (1-diffTime);
};
 

Zoom in and out

When the running time diffTime is less than the set scaleTime, the scale increases with time and the scale becomes larger. When the set time threshold is exceeded, the final size is returned.

const basicScale = [0.6, 0.9, 1.2] [getRandom (0, 2)];
const getScale = (diffTime) => {
    if (diffTime < this.scaleTime) {
        return +((diffTime/ this.scaleTime).toFixed(2)) * basicScale;
    } else {
        return basicScale;
    }
};
 

fade out

The same as the amplification logic, except that the fade-out takes effect at the end of the run.

const fadeOutStage = getRandom (14, 18) / 100;
const getAlpha = (diffTime) => {
    let left = 1 - +diffTime;
    if (left > fadeOutStage) {
        return 1;
    } else {
        return 1 - +((fadeOutStage - left) / fadeOutStage).toFixed(2);
    }
};
 

Real-time drawing

After creating the drawing object, you can draw in real time, according to the "offset value", "zoom in" and "fade out" values ​​obtained above, and then draw the position of the like picture in real time.

In each execution cycle, you need to redraw all the animated picture positions on the canvas, and finally form the effect that all the like pictures are in motion.

createRender(){
    return (diffTime) => {
        // The difference is full, it is over 0 ---》 1
        if(diffTime>=1) return true;
        context.save();
        const scale = getScale(diffTime);
        const translateX = getTranslateX(diffTime);
        const translateY = getTranslateY(diffTime);
        context.translate(translateX, translateY);
        context.scale(scale, scale);
        context.globalAlpha = getAlpha (diffTime);
        // const rotate = getRotate();
        // context.rotate(rotate * Math.PI / 180);
        context.drawImage(
            image,
            -image.width / 2,
            -image.height / 2,
            image.width,
            image.height
        );
        context.restore();
    };
}
 

The picture drawn here is the width and height of the original picture. Earlier we set the basiceScale. If the picture is bigger, we can make the scale smaller.

const basicScale = [0.6, 0.9, 1.2] [getRandom (0, 2)];
 

Real-time drawing scanner

Turn on the real-time drawing scanner and put the created rendering object into the renderList array. The array is not empty, indicating that there is still animation on the canvas, and you need to continue to scan until the animation on the canvas ends.

scan() {
    this.context.clearRect(0, 0, this.width, this.height);
    this.context.fillStyle = "#f4f4f4";
    this.context.fillRect(0,0,200,400);
    let index = 0;
    let length = this.renderList.length;
    if (length > 0) {
        requestAnimationFrame(this.scan.bind(this));
    }
    while (index < length) {
        const render = this.renderList[index];
        if (!render || !render.render || render.render.call(null, (Date.now() - render.timestamp) / render.duration)) {
            // End, delete the animation
            this.renderList.splice(index, 1);
            length--;
        } else {
            // The current animation is not completed, continue
            index++;
        }
    }
}
 

Here is a comparison based on the execution time to determine where the animation is performed:

diffTime = (Date.now() - render.timestamp) / render.duration  
 

If the start timestamp is 10000 and the current time is 100100, it means that it has been running for 100 milliseconds. If the animation originally needs to be executed for 1000 milliseconds, then diffTime = 0.1, which means that the animation has run 10%.

Increase animation

Every time you like it or every time someone likes it, you call the start method to generate a render instance and put it into the render instance array. If the current scanner is not turned on, you need to start the scanner. The scanning variable is used here to prevent multiple scanners from being turned on.

start() {
    const render = this.createRender();
    const duration = getRandom (1500, 3000);
    this.renderList.push({
        render,
        duration,
        timestamp: Date.now(),
    });
    if (!this.scanning) {
        this.scanning = true;
        requestFrame(this.scan.bind(this));
    }
    return this;
}
 

Keep together

When receiving a large number of likes data, and like many times in a row (when the live broadcast is very popular). Then the rendering of the like data needs special attention, otherwise the page is a lot of like animation. And the connection is not tight.

thumbsUp(num: number) {
      if (num <= this.praiseLast) return;
      this.thumbsStart = this.praiseLast;
      this.praiseLast = num;
      if (this.thumbsStart + 500 < num)
        this.thumbsStart = num - 500;
      const diff = this.praiseLast - this.thumbsStart;
      let time = 100;
      let isFirst = true;
      if (this.thumbsInter != 0) {
        return;
      }
      this.thumbsInter = setInterval(() => {
        if (this.thumbsStart >= this.praiseLast) {
          clearInterval(this.thumbsInter);
          this.thumbsInter = 0;
          return;
        }
        this.thumbsStart++;
        this.thumbsUpAni.start();
        if (isFirst) {
          isFirst = false;
          time = Math.round(5000 / diff);
        }
      }, time);
    },
 

Start the timer here, and record the value of thumbsStart processed in the timer. If there is a new like, and the timer is still running, directly update the last prayerLast value, and the timer will process all the like requests in turn.

The delay time of the timer is determined according to how many like animations need to be rendered when the timer is turned on. For example, if 100 like animations need to be rendered, we distribute 100 like animations within 5s.

  • For popular live broadcasts, there are many animations that will be rendered at the same time, and they will not be displayed together, and the animations can be fully connected.
  • For the unpopular live broadcast, there is more than one like request, we can break it up to display within 5s, and will not get together.

End

Both ways of rendering the like animation have been completed, complete source code, source code poke here .

Source code running effect diagram:

 

 

You can also experience the online like animation here , poke here

Compare again

Both of these implementations can meet the requirements, so which one is better?

Let's look at the data comparison between the two. The following is a comparison of the hardware acceleration is not turned on, using non-stop crazy rendering of the comparision animation data comparison:

 

 

Overall, the differences are as follows:

  • CSS3 is simple to implement
  • Canvas is more flexible and more delicate
  • CSS3 memory consumption is greater than Canvas, if you enable hardware acceleration, the memory consumption is greater.


Author: World Multi-
link: https: //juejin.im/post/5e947b8f6fb9a03c957ffd1a
Source: Nuggets
copyright reserved by the authors. For commercial reproduction, please contact the author for authorization, and for non-commercial reproduction, please indicate the source.

Guess you like

Origin www.cnblogs.com/cangqinglang/p/12700139.html