preface

A few days ago to see a big of a paper entitled “you are spoiled VUE, not how to read, probably means to take them by figure, for example, to warn developers out of the framework is to grasp the basis of JS function, want to understand the principle of various libraries to realize more, although there are said to that article by figure of some of the ideas, but in order to achieve a library, There are still a lot of details to pay attention to, so I decided to write this article to revisit the JS version of the wheel cast map.

Knowledge points involved

HTML and CSS are only used to build component containers and styles. ES6 classes, proxies and requestAnimationFrame will be used. In this article, I will also talk about these knowledge points, but I will not go into details.

Implementation approach

One of the most soulful points of seamless wheel casting should beSeamless shufflingThe so-calledSeamless shuffling, is that you click that one, the effect is from the next one or the previous one smooth row past, in a suitable speed, give a person the feeling is very comfortable. The next diagram shows the whole idea.Here are the steps:

  1. Click the button on the right to add the next image to the parent container of the current wheel map.
  2. Change the position of the current two images, i.e. left, so that the position of the second image slides to the first image, with a transition effect.
  3. Destroy the first image to make the second image become the first image.

Whether you swipe left or right, the idea is to put an image in front of/behind the current image, and then move it when it’s completely stuffed. In the image above, I did not add the dot at the bottom of the current image, which would be the same if I jumped from the first image to the fifth.

Code implementation

Basic HTML and CSS

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Document</title>
    <style>
      * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
      }
      .carousel {
        position: relative;
        margin: auto;
        overflow: hidden;
      }
      .imgCon {
        position: absolute;
        left: 0;
        right: 0;
      }
    </style>
  </head>
  <body>
    <div class="carousel">
      <! -- The outermost container of the multicast diagram -->
      <div class="imgCon"></div>
      <! -- The container of the image in the rotation diagram -->
    </div>
    <script>
      /* The JS code is here */
    </script>
  </body>
</html>
Copy the code

Carousel is the total container of the rote graph, which adopts relative positioning. ImgCon is a container for scrolling images, and uses absolute positioning because we are adding the DOM to the container when we switch images. Let’s start writing the JS code

Setting container Parameters

<script>
  /* The JS code is here */
  const config = {
    img: [
      "img/a.jpeg"."img/b.jpeg"."img/c.jpeg"."img/d.jpeg"."img/e.jpeg",].leftClick: "img/left.png".rightClick: "img/right.png".autoPlay:true.autoPlayTime:300.time: 1500};class Banner {
    constructor(config) {
      if (typeofconfig ! = ="object" || config === null) {
        throw Error("config must be a object");
      }
      const { img, leftClick, rightClick, autoPlay, time, autoPlayTime } = config;
      this.img = img;
      this.leftClick = leftClick;
      this.rightClick = rightClick;
      this.autoPlay = autoPlay;
      this.time = time;
      this.getSize();
      this.createCacheImage();
    }

    getSize() {
      this.WIDTH = document.body.clientWidth;
      this.HEIGHT = this.WIDTH / 3;
    }

    createCacheImage() {
      this.bannerCache = this.img.map((src) = > {
        const img = new Image();
        img.src = src;
        const style = {
          width: this.WIDTH + "px".height: this.HEIGHT + "px"}; Banner.setStyle(img, style);return img;
      });
      this.btnCache = [this.leftClick, this.rightClick].map((src,index) = > {
        const img = new Image();
        img.src = src;
        const className = index === 0 ? "left" : "right";
        img.type = className;
        img.setAttribute('class'.`${className}_btn`)
        returnimg; }); }}new Banner(config);
</script>
Copy the code
  1. Config is the required parameters of the multicast map, including the URL of the multicast map, the URL of the buttons on both sides (that is, the previous one/the next one), whether it is automatic multicast and the time required for image switching.
  2. We store all of our configuration parameters in this and take them as they come in.
  3. getSizeThe size of the rotation chart set for me, the reason for choosing this size is that my own picture is relatively large, which is more comfortable to watch in full screen. The height is also determined by the picture. Save the width and height in this, which will be used later.
  4. createCacheImageSave the image and button image in a Map. When we switch to the next image, we can pull it out of the Map without re-creating the image DOM and setting the style of each image. inbtnCacheIn the loop, we set the type of the two buttons to distinguish between left and right, in order to easily distinguish between them in the click event; Set it to a different class name so that the style can be positioned on both sides of the rotation diagram (CSS code is not pasted here).
class Banner {
 static addEvent = (type, callback, target = window) = > {
  return target.addEventListener(type, callback);
 };

static setStyle = (element, style = {}) = > {
  Object.assign(element.style, style);
  return element;
};

constructor(config) {
  if (typeofconfig ! = ="object" || config === null) {
    throw Error("config must be a object");
  }
  const { img, leftClick, rightClick, autoPlay, time } = config;
  this.img = img;
  this.leftClick = leftClick;
  this.rightClick = rightClick;
  this.autoPlay = autoPlay;
+ Banner.addEvent("resize".this.updateSize.bind(this));
  this.time = time;
  this.getSize();
  this.createCacheImage();
+ this.setBannerSize();
}
// The following is new, the previous function method is unchanged

updateSize() {
  this.WIDTH = document.body.clientWidth;
  this.HEIGHT = this.WIDTH / 3;
}

setBannerSize() {
  if(!this.carousel){
   this.carousel = document.querySelector(".carousel");
  }
  const style = {
    width: this.WIDTH + 'px'.height: this.HEIGHT + 'px'
  }
  Banner.setStyle(this.carousel, style); }}Copy the code
  1. setStyleandaddEventIs a common method to encapsulate, adding a DOM style and adding a DOM event, respectively, becauseThese two methods do their job by passing arguments to the function and do not need to get any information about the current this, so usestaticThe keywords are written as static methods.
  2. forwindowaddresizeEvents are made compatible with the possibility that the window size changes at any time and the width changes when updatedwidthandheight.
  3. setBannerSizewillcarouselThat is, the total container setting width and height of the rotation chart.

Add images

class Banner {
// Static methods are omitted
constructor(config) {
 // omit the previous method
 + this.appendImage();
 + this.appendBtn();
}
// The following is new, the previous function method is unchanged

appendImage(currentImage = 0) {
  if (typeofcurrentImage ! = ="number") {
    return;
  }
  if (!this.imgCon) {
    this.imgCon = document.querySelector(".imgCon");
    const style = {
      width:this.WIDTH + 'px'.height: this.HEIGHT + 'PX',}this.setStyle(this.imgCon, style);
    this.carousel.appendChild(this.imgCon);
  }
  return this.imgCon.appendChild(this.bannerCache[currentImage]);
}

appendBtn() {
  this.btnCache.forEach((btn) = > {
    Banner.addEvent("click".this.handleBtnClick.bind(this), btn);
    this.carousel.appendChild(btn);
  });
}

handleBtnClick(e){ 
 / / to omit}}Copy the code
  1. appendImageAdd the current NTH image to the parent container of the carousel graph, here we also inHTMLThe DOM is stored directly in this to facilitate the next operation. So here we have parametercurrentImageIndicates the number of images to add, because we have cached all the images in rotationbannerCache, so it can be indexed frombannerCacheNext, if we click the dot at the bottom of the wheel map, we can add it directly through its index.
  2. appendBtnomithandleBtnClickDetails.

At this point we can look at what we have Ok, that’s basically what it looks like, but the problem is that when I change the window size, the size of the wheel map container doesn’t change.The reason is we listened in on Window’sresizeEvent, and dynamically modifiedthis.WIDTHandthis.HEIGHTBut the DOM size does not change dynamically if you manually change its value. (What about the sudden nostalgia for the benefits of framing?) We’re going to listen in nextWIDTHandHEIGHTThe change.

Using a Proxy to listen

class Banner {
  // The above is omitted
  static bindProxy = (object, callback, targetChangeCallback) = > {
    if (typeofobject ! = ="object" || object === null) return;
    const proxy = new Proxy(object, callback(targetChangeCallback));
    return proxy;
  };
  
  constructor(){
    / /... The above is omitted
    Banner.addEvent("resize".this.getSize.bind(this)); // Set the resize callback to getSize
    this.sizeInfo = {};
    this.sizeInfo = Banner.bindProxy(
    this.sizeInfo,
    this.sizeInfoChange,
    this.sizeInfoChangeCallback.bind(this));this.getSize() // write this.getSize after Proxy
  }
  
  getSize() {
    this.WIDTH = document.body.clientWidth;
    this.HEIGHT = this.WIDTH / 3;
    this.sizeInfo.width = this.WIDTH;
    this.sizeInfo.height = this.HEIGHT;
  }
  
Kill, / * this function has no meaning updateSize () {this. WIDTH = document. Body. ClientWidth; this.HEIGHT = this.WIDTH / 3; }) * /  
  createCacheImage() {
    this.bannerCache = this.img.map((src) = > {
      const img = new Image();
      img.src = src;
      /* const style = { width: this.WIDTH + "px", height: this.HEIGHT + "px", }; * /
      Banner.setStyle(img, this.sizeInfo);
      return img;
    });
    this.btnCache = [this.leftClick, this.rightClick].map((src) = > {
      const img = new Image();
      img.src = src;
      return img;
    });
  }
  
  setBannerSize() {
    if(!this.carousel){
     this.carousel = document.querySelector(".carousel");
    }
    /* const style = { width: this.WIDTH + "px", height: this.HEIGHT + "px", }; * /
    Banner.setStyle(this.carousel, this.sizeInfo);
  }
  
  appendImage(currentImage = 0) {
    if (typeofcurrentImage ! = ="number") {
      return;
    }
    if (!this.imgCon) {
      this.imgCon = document.querySelector(".imgCon");
      /* Banner.setStyle(this.imgCon, { width: this.WIDTH + "px", height: this.HEIGHT + "px", }); * /
      Banner.setStyle(this.imgCon, this.sizeInfo);
      this.carousel.appendChild(this.imgCon);
    }
    return this.imgCon.appendChild(this.bannerCache[currentImage]);
  }
  
 sizeInfoChange(callback) {
  if (callback && typeofcallback ! = ="function") {
    throw Error("callback must be a function!");
  }
  return {
    get: (target, key) = > {
      return target[key];
    },
    set: (target, key, value) = > {
      if(! ["width"."height"].includes(key)) {
        throw Error("must be width or height");
      }
      if (typeofvalue ! = ="number") {
        throw Error("must be type of number");
      }
      const change = Reflect.set(target, key, value + "px");
      if (key === "height") {
        callback && callback();
      }
      returnchange; }}; } handleBtnClick =() = > {
  / / to omit
}

sizeInfoChangeCallback() {
  this.setBannerSize();
  this.bannerCache.forEach((img) = > {
    Banner.setStyle(img, this.sizeInfo); }); }}Copy the code
  1. Just to make it simpleProxyUsage:

Proxy can be understood as a layer of “interception” before the target object. All external access to the object must pass this layer of interception. Therefore, Proxy provides a mechanism for filtering and rewriting external access. The word Proxy is used to mean that it acts as a Proxy for certain operations.

var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}! `);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}! `);
    return Reflect.set(target, propKey, value, receiver); }}); obj.count =1
// setting count!
++obj.count
// getting count!
// setting count!
//  2
Copy the code

andObject.definePropertyThere are similar, but Proxy is better, here I do not elaborate, digging gold searchProxyThere are several good articles about it in detailProxyThe principle and use of.

In short, here we areresizeEvent to change the width and height values of sizeInfo to make the callback functionsizeInfoChangeCallbackTrigger to change the size of the outermost container of the multicast picture and the size of each multicast picture.

2.this.getSizeMove to theBanner.bindProxyAfter that, because we listen on sizeInfo and then passgetSizeGet size, modify sizeTo set the width and height of the container and the image when it is first loaded. ifgetSizeinBanner.bindProxyBefore, we had the size before we listened to sizeInfo, and we used all of thatwidthandheightThe whole place has been changedsizeInfo, then the first load will not getwidthandheightInformation.

The container and image sizes now change as the screen changes

Seamless shuffling

The following is the core function of the wheel cast graph:Seamless shuffling. When we click the left and right buttons, it seamlessly switches to the previous/next slide.

When clicking the left button, add the previous image in front of the current image and slide the container of the wheel image to the right.Click the right button and reverse.

The new node

Next we complete the previously empty button event.

class Banner{
  constructor(config){
    / /... All the above code remains unchanged
    this.currentImage = {
     index: 0.// The currently displayed image
    };
    this.currentImage = Banner.bindProxy(
      this.currentImage,
      this.currentImageChange,
      this.sizeInfoChangeCallback.bind(this));this.isPlaying = false; // Whether it is moving
  }
  
  handleBtnClick(){
    if (this.isPlaying) return;
    e.preventDefault();
    e.stopPropagation();
    const { type } = e.target;
    let currentImage;
    this.direction = type;
    if (type === "left") {
      currentImage = this.currentImage.index - 1;
      if (currentImage < 0) {
        currentImage = this.bannerCache.length - 1;
      }
      return (this.currentImage.index = currentImage);
    }
    if (type === "right") {
      currentImage = this.currentImage.index + 1;
      if (currentImage >= this.bannerCache.length) {
        currentImage = 0;
      }
      return (this.currentImage.index = currentImage); }}currentImageChange(callback){
    if (callback && typeofcallback ! = ="function") {
      throw Error("callback must be a function!");
    }
    return {
      get: (target, key) = > {
        return target[key];
      },
      set: (target, key, value) = > {
        if(key ! = ="index") {
          throw Error("you can set `index` property at currentImage");
         }
         if (typeofvalue ! = ="number") {
          throw Error("must be type of number");
        }
        const change = Reflect.set(target, key, value);
        callback && callback();
        returnchange; }}; }currentImageChangeCallback() {
    const type = this.direction;
    this.imgCon.style.width = this.WIDTH * 2 + "px";
    if (type === "left") {
      this.imgCon.insertBefore(
        this.bannerCache[this.currentImage.index],
        this.imgCon.firstElementChild
      );
      this.imgCon.style.left = -this.WIDTH + "px";
    } else {
      this.appendImage(this.currentImage.index);
    }
    this.isPlaying = true; }}Copy the code
  1. See firstthis.currentImage, because we switch the image to switch the current index, that is, to switch the index of the bannerCache, so when the index changes, we operate the same as when listening for the change of the width and height, useProxyListen for changes in the current picture index.
  2. this.isPlayingIndicates whether the current is in the rotation switching, if so, block all operations, because if the button is continuously clicked when switching pictures, frequent operations will affect the interaction.
  3. seehandleBtnClickWe added the code for the left and right buttonstypeProperty is now used. If you click the left button, save the direction of the click tothis.directionIn, we’re going to use. Click the left button to switch to the previous image. Assuming the current index is 2, which is the third image displayed, you should add the second image to the front of the current image, swipe right, and click the right button to reverse. So now we have to pay attention to the boundary conditions, which are the 0th and the last card.
  4. Click on which button to switch to which graph we get this result and assign it tocurrentImagetheindexProperties,ProxyThe callback function incurrentImageChangeCallbackThe trigger.
  5. Because no matter left or right, it will add a graph to the current carousel graph container, so you need to set the width of the current container to 2 times of the original, and then save according to the previousdirectionTo determine whether to add images forward or backward.
  6. Have a style problems need to pay attention to, if you click the left button, to the current picture to add a node, the node before if increase, the success of new nodes will directly to the current node into the back, instead of display is a new node, we have to manually handle the distance of the left, and then through the animation will show the above actions.

Let’s click the left and right buttons respectively:

Click the left button: Is added before the current node

Click the button on the right: adds after the current node

RequestAnimationFrame Animation transition

class Banner{
  constructor(config){
  	/ /... Omit the above code
    this.FPS = 60;
    this.getFPS();
    this.computeSpeed()
    this.animation();
  }
  
  getFPS() {
    let now = Date.now();
    let lastNow = Date.now();
    let count = 0;
    let countTotal = 0;
    const countArr = [];
    let computeFrame = null;
    const compute = () = > {
      now = Date.now();
      count++;
      computeFrame = requestAnimationFrame(compute);
      if (now - lastNow >= 1000) {
        lastNow = Date.now();
        const length = countArr.push(count);
        countTotal = countTotal + count;
        count = 0;
        if (length === 5) {
          this.FPS = Math.round(countTotal / 5); cancelAnimationFrame(computeFrame); }}}; compute(); }computeSpeed(){
    const rCount = 1000 / this.FPS;
    this.speed = this.WIDTH / (this.time / rCount);
  }
  
  animation() {
    this.callback = requestAnimationFrame(this.animation.bind(this));
    this.carouselPlaying();
  }
  
  carouselPlaying() {
    if (!this.isPlaying) return;
    const direction = this.direction;
    const imgCon = this.imgCon;
    if (direction === "left") {
      imgCon.style.left = imgCon.offsetLeft + this.speed + "px";
      if (imgCon.offsetLeft >= 0) {
        this.isPlaying = false;
        imgCon.lastElementChild.remove();
        imgCon.style.left = "0px"; }}else if (direction === "right") {
      imgCon.style.left = imgCon.offsetLeft - speed + "px";
      if (imgCon.offsetLeft <= -this.WIDTH) {
        this.isPlaying = false;
        imgCon.firstElementChild.remove();
        imgCon.style.left = "0px"; }}}}Copy the code

After we add a new node, we need to move the container of the current multicast diagram left/right to make the new node visible. Here we use requestAnimationFrameAPI. It in MDN explanation: window. RequestAnimationFrame () telling the browser – you want to perform an animation, and require the browser until the next redraw calls the specified callback function to update the animation. This method takes as an argument a callback function that is executed before the browser’s next redraw. That is, the browser calls its callback every time it redraws, and the redraw rate is the screen refresh rate, or FPS, which is the number of screen refreshes per second. Generally, our screen refresh rate is 60HZ, which is 60 times per second, with an average refresh rate of 16.7 milliseconds, but we don’t know that the rotation graph really runs on a multi-Hz screen, so we need to calculate here. We passed time = 1500ms in the initial config parameter, so we need to complete the rotation action within 1500ms, then calculate:

  1. Assuming a screen refresh rate of N, the number of milliseconds required for a refresh = 1000/ refresh times per second (N)
  2. 1500 ms Number of times to refresh = 1500 / Number of milliseconds to refresh
  3. So the distance to be moved per refresh = the total distance to be moved/the number of refreshes to be moved

computeSpeedSet the screen refresh rate at60HZThe case calculates the speed of each refresh, while the actual FPS of the screen is also being calculated, throughgetFPSIn the functioncomputeThe number of times the function runs per second, take the average of 5 times can get the accurate FPS, after the FPS is calculated againspeed.

Run at initialization timeanimationFunction for internal userequestAnimationFrameThe callback itself andcarouselPlaying, if one of the left/right buttons is clicked,this.isPlayingA value fortrue.carouselPlayingIn thethis.isPlayingOnce fortrueSubsequent code can then be run in each frame, as judgeddirectionDo I move to the left or right, do I move,carouselPlayingThis is run every 16.7ms until the next image to be displayed is pulled to the current position, and then the last one is destroyed. The whole rotation process is complete.Note: In the process of moving, we use offsetLeft, that is, the distance between the edge of the parent container and the total container. Calculate the distance to move, and then change the left value of the parent container of the multicast diagram. The following figure clearly shows the meaning of offsetLeft Let’s take a look at the rotation effect now

Dot jump on

Do you feel like there’s something missing from this rotation chart? Huh? That’s right, it’s the dot below the rotation chart, no dot progress, can only play one by one, if from the first directly jump to the third? And then we’re going to add little dots. Add a dot to the HTML container and style it

<style>
  * {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
  }
  .carousel {
    position: relative;
    margin: auto;
    overflow: hidden;
    margin: 30px auto;
  }
  .imgCon {
    position: absolute;
    left: 0;
    right: 0;
  }
  .left..right {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
  }
  .left {
    left: 10px;
  }
  .right {
    right: 10px;
  }
  /* Dot parent container */
  .processDot {
    position: absolute;
    bottom: 20px;
    width: 200px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    justify-content: space-around;
    z-index: 2;
  }
  /* The style of each dot */
  .process_dot {
    list-style: none;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 1px solid red;
  }
  /* If the current round image index is the same as the dot index */
  .process_dot[active="true"] {
    background-color: red;
  }
</style>
<div class="carousel">
  <! -- The outermost container of the multicast diagram -->
  <div class="imgCon"></div>
  <! -- The container of the image in the rotation diagram -->
  <ul class="processDot"></ul>
</div>
Copy the code
class Banner{
  constructor(){
    / /... Omit all of the above
    this.appendDot();
  }
  
  handleDotClick(index, e) {
    e.preventDefault();
    e.stopPropagation();
    if (this.isPlaying) return;
    if (index > this.currentImage.index) {
      this.direction = "right";
    }
    if (index < this.currentImage.index) {
      this.direction = "left";
    }
    this.currentImage.index = index;
  }
  
  appendDot() {
    this.processDot = document.querySelector(".processDot");
    const fragment = document.createDocumentFragment();
    this.dotCache = this.bannerCache.map((img, index) = > {
      const dot = document.createElement("li");
      dot.setAttribute("class"."process_dot");
      if (index === this.currentImage.index) {
        dot.setAttribute("active".true);
      }
      Banner.addEvent(
        "click".this.handleDotClick.bind(this, index),
        dot
      );
      fragment.appendChild(dot);
      return dot;
    });
    this.processDot.appendChild(fragment);
  }
  
  currentImageChangeCallback() {
    const type = this.direction;
    this.imgCon.style.width = this.WIDTH * 2 + "px";
    if (type === "left") {
      this.imgCon.insertBefore(
        this.bannerCache[this.currentImage.index],
        this.imgCon.firstElementChild
      );
      this.imgCon.style.left = -this.WIDTH + "px";
    } else {
      this.appendImage(this.currentImage.index);
    }
    const index = this.currentImage.index;
   + this.dotCache.forEach((dot, dotIndex) = >{+if (index === dotIndex) {
   +     return dot.setAttribute("active".true);
   +  }
   +   dot.setAttribute("active".false); +});this.isPlaying = true; }}Copy the code
  1. Create dots according to the number of images being rotated, set the dots corresponding to the index of the currently being rotated pictureactiveProperty and add click events for each dot
  2. When clicking the dot, judge the index relationship between each dot and the index of the current carousel graph, find the direction that should be carousel, and assign the index value to the picture to be carousel to.
  3. incurrentImage.indexCallback function to increase the dot index and the current multicast index, if equal, thenactivefortrueOtherwise, it is false.

Take a look at the results:So far, we’re 85% complete with the rotation chart, so what’s left? That’s right, automatic rotation!When the page stays & the mouse is outside the wheel cast mapWe do automatic rotation. Why? Because once the mouse hover above the wheel, there is a high probability that the user will perform some wheel operations, such asClick on events for a larger view of events….

Automatically round

class Banner{
  static removeEvent = (type, callback, target = window) = > {
    return target.removeEventListener(type, callback);
  };
  constructor(){
    / /... omit
    + this.autoPlayTime = autoPlayTime/this.rCount;
    + this.cuteTime = this.autoPlayTime;
    + this.autoPlayEvent();
    + this.addMouseEvent();
  }
  
  autoPlayEvent() {
    if (this.autoPlay) {
      this.autoPlaying = true; }}addMouseEvent() {
    if (this.autoPlay) {
      Banner.addEvent(
        "mouseenter".this.handleMouse.bind(this),
        this.carousel
      );
      Banner.addEvent(
        "mouseleave".this.handleMouse.bind(this),
        this.carousel ); }}handleMouse(e) {
    if (e.type === "mouseenter") {
      this.autoPlaying = false;
    }
    if (e.type === "mouseleave") {
      this.autoPlaying = true; }}computeSpeed() {
    //const rCount = 1000 / this.FPS;
    this.rCount = 1000 / this.FPS;
    this.speed = this.WIDTH / (this.time / this.rCount);
  }
  
  animation() {
    this.callback = requestAnimationFrame(this.animation.bind(this));
    this.carouselPlaying();
  + this.handleAutoPlay();
  }
  
  handleAutoPlay() {
    if (!this.autoPlaying) return;
    this.cuteTime--;
    if (this.cuteTime > 0) return;
    this.cuteTime = this.autoPlayTime;
    var evt = new MouseEvent("click");
    this.btnCache[1].dispatchEvent(evt); }}Copy the code
  1. Let’s start with what’s newautoPlayEventFunction, judgmentautoPlayIf yes, automatic rotation is required. If yes, turn on the automatic rotation switchautoPlaying.
  2. animationA callback function has been added to the functionhandleAutoPlay, is to let the picture for automatic rotation use, internal logic isCheck whether the autoPlaying switch is enabled. If it is enabled, rotate once at that time according to the passed time
  3. becauseautoPlayTimeIt is how many seconds the user wants to have a round cast, and our keyframe movement is calculated, if 60PFS, it is executed every 16.7mshandleAutoPlayAnd thethis.cuteTimeWe just subtract 1 each time, so we need to calculate the rotation time to reach the user’s desired number.
  4. Automatic rotation we use custom events, because the buttons on the left and right sides already have click events for rotation, so we only need to trigger button click events on the right side to carry out rotation.
  5. Add mouse entry and remove events to the whole wheel map, because once the mouse hover on the wheel map, then the user’s purpose is very likely to click on the event, then should stop wheel.

Finally, the rotation diagram is complete, let’s look at the final result ohshit! Gifs are too big for nuggets to fit in.

The code address is presented first

conclusion

To summarize the various details of the wheel cast diagram:

  1. useProxyInstead of traditional callback functions, executing events by listening for changes to the data allows better decoupling of the code.
  2. requestAnimationFrameIn lieu of traditionalsetTimeout, theoretically making animation silky and saving performance.
  3. Caches image values and does not request images again each time round. Side also saves performance.
  4. Consider boundary conditions, such as the need to prohibit the rotation event to be executed again during the process of rotation, mouse hover to prohibit automatic rotation.

In the process of writing, the author also thought that when creating images should use Promise actually, wheel pictures all loading the whole round successfully replayed figure can be created, because the picture loaded asynchronously, and if the loading time is too long the first picture so JS code error occurs, one of the images load, failure to consider the corresponding treatment method.