preface

Recently, I have been doing some growth hacking, and my business may involve making H5 pages that can be widely shared in wechat moments. It suddenly occurred to me that last year, I saw an H5 page for the annual news review of netease Entertainment, which was very novel, using more than ten hand-painted entertainment pictures in the form of drawing in picture. With the brainwashing “Good luck come” music, there is a great desire to share.

Mobile Phone Scan experience netease annual Entertainment Inventory:

Step by step

Next, we will implement such a H5 page step by step. First, we need to understand the front-end knowledge of this page.

CSS 3 animation animation

There are many animations on the first screen, most of which are implemented using the Sprite + Animation step function, including the drum on the bottom, the cymbals on the upper right, and the floating hair of the characters in the middle. The waves rolling back and forth under your feet are ordinary animation. In addition to these animations on the first screen, there will also be animations when switching to certain scenes, which are also Sprite animations.

The background music

Use the audio element, set audio to loop, and call Audio-pause () when clicking on the hi-hat GIF in the upper right corner.

Scene: the switch

On the first screen, when the drum is pressed long, the animation on the page will stop, and the static picture will shrink little by little until the first complete picture in picture appears. At this point, the transition animation stops and the page animation appears.

So let’s start with this little piece of code, what we’re going to do.

First, we need two layers, one Canvas layer to show the scene transition animation, with a lower Z-index. A layer that shows the scene animation, which we call the GIF layer, has a higher Z-index;

drawImage

On the Canvas layer, we use the drawImage() method to draw the transition image for each frame. Let’s first look at how this method is used:

context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);

The parameter value

parameter describe
img Specify the image, canvas, or video to use.
sx Optional. The x position at which the shear began.
sy Optional. The y position where the shear started.
swidth Optional. The width of the clipped image.
sHeight Optional. The height of the clipped image.
x Place the x position of the image on the canvas.
y Place the y coordinate position of the image on the canvas.
width Optional. The width of the image to use. (Stretch or zoom out image)
height Optional. The height of the image to use. Stretch or zoom out the image.

For each frame of the transition animation, we use drawImage to draw two images on the canvas, one is the large image and the other is the small image in the painting. Take the first transition animation as an example, the large image is P2 and the small image is P1.

Picture :(forgive me for not knowing what tool to use for drawing, so I have to do it)

Let’s assume that the big picture P2 is a rectangle ABCD and the small picture P1 is a rectangle IJKL, and the mobile phone screen at a certain point in the animation process is a rectangle EFGH. We have a premise that all three rectangles have a width to height ratio of 750: 1206 is a rectangle, and all pictures are of the same width, height and pixel size (netease’s scene picture size is 1875*3015), which also means that iPhone X and other full-screen phones will have some problems in adaptation, which performs well on iPhone678. (See if netease can solve this problem this year, as there are more and more full-screen phones.)

So, at a moment like this, we need to draw two pictures on the canvas,

DrawImage (P2, ME, NE, EF, EH, 0,0,750,1206) drwaImage (P1, 0, 0, AB, AD, OI, PI, IJ, IL)Copy the code

So we know what’s going on at a moment in time, but how do we move the picture and have a shrinking effect?

Now start writing our render function:

const render = () => { this.radio = this.radio * this.scale; this.timer = requestAnimationFrame(render); this.draw(); // Draw two images}; draw() { if (this.index + 1 ! = this.imgList.length) { if ( this.radio < this.imgList[this.index + 1].areaW / this.imgList[this.index + 1].imgW ) { if  (this.willPause) { this.radio = this.imgList[this.index + 1].areaW / this.imgList[this.index + 1].imgW; cancelAnimationFrame(this.timer); } this.index++; this.radio = 1; if (! this.imgList[this.index + 1]) { this.showEnd(); } } this.imgNext = this.imgList[this.index + 1]; this.imgCur = this.imgList[this.index]; this.containerImage = this.domList[this.index + 1]; this.innerImage = this.domList[this.index]; this.drawImgOversize( this.containerImage, this.imgNext.imgW, this.imgNext.imgH, this.imgNext.areaW, this.imgNext.areaH, this.imgNext.areaL, this.imgNext.areaT, this.radio, ), this.drawImgMinisize( this.innerImage, this.imgCur.imgW, this.imgCur.imgH, this.imgNext.imgW, this.imgNext.imgH, this.imgNext.areaW, this.imgNext.areaH, this.imgNext.areaL, this.imgNext.areaT, this.radio, ); }}Copy the code

The render function has two variables radio and scale, radio = IJ/EF, so in a scene switch animation, we only need to change the value of radio from 1 to IJ/AB. Scale is such a constant for the rate at which radio changes. We can define this as 0.99, since requestAnimationFrame’s callback will be executed about 60 times a second in the browser, and.99^240 = 0.08, so we should be able to complete a scene switch in about 4s, which is still a reasonable speed.

Thus, at any point in the animation, EF’s size can be expressed as IJ/this.radio, and since all the images are made by our artist, The pixel size (imgW, imgH) of each image, the offset position of the small image in the large image SI(areaL), TI(areaT), and the width and height of the small image IJ(areaW) and IL(areaH) are all known. According to these known data, We can easily (for those of you who are good at math) represent the unknown variable in the drawImage as this.radio.

ImgNext and imgCur should be reassigned to index++ when this.radio reaches critical value.

Finally, write the render function into the touchHandler.

touchHandler(e) {
    e.stopPropagation();
    // e.preventDefault();
    const render = () => {
        this.radio = this.radio * this.scale;
        this.timer = requestAnimationFrame(render);
        this.draw();
    };
    cancelAnimationFrame(this.timer);
    this.willPause = false;
    // clearInterval(this.gif_timer);
    this.timer = requestAnimationFrame(render);
}
Copy the code

GIF animation

GIF animation, but the implementation of Sprite diagram +step implementation. If there is an animation in the scene, then at the end of the transition animation, I can show the GIF layer. The GIF layer consists of two parts, one is the background image and the other is the animation area. The background image will leave the animation area blank, and the animation area adopts Sprite diagram + STEP to realize the animation. This is done to reduce image resource size and speed up loading.

Loading pictures

The H5 page needs to load a large number of images, and these images must be loaded before user interaction, so we need to initialize the page to a load state, when all images are loaded, we can display the interactive page. So, we need to know when the image has been loaded, go to the code:

loadGifImg() { const loadPromises = this.gifImgs.map( item => new Promise((resolve, reject) => { const img = new Image(); img.src = item; img.onload = () => resolve(img); img.onerror = () => reject(); })); return Promise.all(loadPromises); } loadPageImg() { const loadPromises = this.imgList.map( (item, index) => new Promise((resolve, reject) => { const img = new Image(); img.src = item.link; img.i = index; img.name = index; img.className = 'item'; item.image = img; img.onload = () => { $('.collection').append(item.image); resolve(); }; img.onerror = () => reject(); })); return Promise.all(loadPromises); }Copy the code

Therefore, we just need to wait for these two Promise resolve to complete the load.

The last

Complete code github welcomes star

Wechat scanning experience

If you think the article is good, you might as well point a concern to go ah ~