Project upgrade & pit filling

instructions

I decided to restart the wheel project after a gap of more than half a year for several reasons:

  1. The old project structure was too bloated
  2. There are some big problems in the implementation of components
  3. Packing method is troublesome

So I created a new project called Tiny-Wheels with the same intention as the old project. The main purpose is to learn, research and summarize the technology personally, and then explore ways to implement some complex components using native JavaScript

Note: the old project has been abandoned, please use the new project directly!

Unit testing of each component has not been completed to ensure stability of the component, so it is not recommended for production

Source code of this project: source code

Detailed usage and effects of each component can be found in the project’s documentation: Documentation

Star ~(IN ω Ed)

Fill in the pit

Structure optimization

Because the packaging method of the old project is not very convenient, so this project re-uses the latest version of Webpack and Babel construction, adopts UMD modular standard packaging, and is compatible with a variety of introduction methods at the same time, both the development and use of a lot of convenient, specific use methods can be viewed in the document related instructions

Components to rewrite

With the power of Webpack, we don’t need to write code in the old syntax, so new projects are written in the latest syntax.

The old project paging and the implementation of the wheel is bloated, and there are many problems in the implementation of the wheel map, this time also re-implement the two components again, the basic function of the component has been completed, you can view the effect and source code in the document

Pager

The previous logic of this component was not too much of a problem; the core principle was the same, but it was implemented in a simpler way

The page number for1To show1 2 3.10The page number for2To show1 2 3 4.10The page number for3To show1 2 3 4 5.10The page number for4To show1 2 3 4 5 6.10The page number for5To show1.3 4 5 6 7.10The page number for6To show1.4 5 6 7 8.10The page number for7To show1.5 6 7 8 9 10The page number for8To show1.6. 7 8 9 10The page number for9To show1.7 8 9 10The page number for10To show1.8 9 10
Copy the code

To achieve this structure, the method used in the old project was:

function showPages(page, total, show) {
  var str = page + "";
  for (var i = 1; i <= show; i++) {
    if (page - i > 1) {
      str = page - i + "" + str;
    }
    if (page + i < total) {
      str = str + ""+ (page + i); }}if (page - (show + 1) > 1) {
    str = "... " + str;
  }
  if (page > 1) {
    str = 1 + "" + str;
  }
  if (page + show + 1 < total) {
    str = str + "...";
  }
  if (page < total) {
    str = str + "" + total;
  }
  return str;
}
Copy the code

The if-else structure is too much to maintain, so we can use the reduce method provided by ES6 to optimize this code logic:

getPager () {
  const pages = [
    1.this.pageCount,
    this.pageCurrent,
    this.pageCurrent - 1.this.pageCurrent - 2.this.pageCurrent + 1.this.pageCurrent + 2
  ]
  const pageNumbers = [
    ...new Set(
      pages.filter(n= > n >= 1 && n <= this.pageCount).sort((a, b) = > a - b)
    )
  ]
  const pageItems = pageNumbers.reduce((items, current, index, array) = > {
    items.push(current)
    if (array[index + 1] && array[index + 1] - array[index] > 1) {
      items.push('... ')}return items
  }, [])
  return pageItems
}
Copy the code

The principle is exactly the same, only more to repeat, sort and other steps of operation, but the code has been greatly simplified if there is a better implementation ideas, welcome to give me suggestions ~

Carousel

In the old project, the implementation idea of the casting component was like this:

  1. Use JavaScript to calculate the ordinal number of child elements
  2. useposition: absolute + leftortransformControls the position of the parent element to animate the movement

This is also one of the most common implementations you’ll find on the web, but a little testing reveals a number of flaws:

  1. Frequent DOM operations result in low performance
  2. Flicker in some browser renderings
  3. Animation algorithms are not conducive to maintenance and function expansion

After reading the source code for some of the larger and more mature UI frameworks, such as Element-UI, iView, Ant-Design, and Bootstrap, it turns out that they all use a more “advanced” and clever approach

In fact, to achieve the animation effect of rotation, there is no need to calculate the sequence number of each child element and the corresponding order change, because no matter which direction, all we see is the movement effect of two child elements, so we only need to add the corresponding style to the two moving elements

The specific implementation idea is to use the transform attribute of CSS3 to control the position change of child elements, and add the transition animation with transition. In JS code, you only need to add the corresponding class name at the appropriate time, and then remove the corresponding class name

First, we need to calculate the ordinal number of the two elements that are currently transitioning:

getCurrentIndex () {
  return [...this.?dots].indexOf(
    this.$container.querySelector('.carousel-dot.active')
  )
}

getPrevIndex () {
  return((this.getCurrentIndex() - 1 + this.? dots.length) %this.? dots.length ) } getNextIndex () {return (this.getCurrentIndex() + 1) % this.? dots.length }Copy the code

We can then add the corresponding class names to the two elements:

setCarouselPanel ($from, $to, direction) {
  this.isAnimate = true
  window.requestAnimationFrame((a)= > {
    const { fromClass, toClass } = this.resetCarouselPanel($to, direction)
    window.requestAnimationFrame((a)= > {
      this.moveCarouselPanel(fromClass, toClass, $from, $to)
    })
  })
}
resetCarouselPanel ($to, direction) {
  let fromClass = ' '
  let toClass = ' '
  const type = direction === 'left' ? 'next' : 'prev'
  $to.setAttribute('class'.`carousel-panel ${type}`)
  fromClass = `carousel-panel active ${direction}`
  toClass = `carousel-panel ${type} ${direction}`
  return { fromClass, toClass }
}
moveCarouselPanel (fromClass, toClass, $from, $to) {
  $from.setAttribute('class', fromClass)
  $to.setAttribute('class', toClass)
  setTimeout((a)= >{$from.setAttribute('class'.'carousel-panel')
    $to.setAttribute('class'.'carousel-panel active')
    this.isAnimate = false
  }, this.duration)
}
Copy the code

This is a very difficult problem to solve, because the idea is to reset the style of the two elements and then add the new style, so we need to add the class name twice, and then apply their style to the corresponding elements in turn. At first, I did it like this:

let fromClass = "";
let toClass = "";
const type = direction === "left" ? "next" : "prev";
$to.setAttribute("class".`carousel-panel ${type}`);
fromClass = `carousel-panel active ${direction}`;
toClass = `carousel-panel ${type} ${direction}`;
setTimeout((a)= >{$from.setAttribute("class", fromClass);
  $to.setAttribute("class", toClass);
}, 0);
setTimeout((a)= >{$from.setAttribute("class"."carousel-panel");
  $to.setAttribute("class"."carousel-panel active");
  this.isAnimate = false;
}, 400);
Copy the code

The first setTimeout is to prevent the browser from automatically merging styles, and the second setTimeout is to clear styles when the animation is finished, and the 400ms is just the transition time. At first glance, it seems that there is no big problem, but in the actual test, there are many weird bugs. After checking for a long time, we finally locate the problem with the first setTimeout

The reason is that using setTimeout does not guarantee 100% that styles will not be merged automatically. Browsers will still merge styles automatically in some very special cases, such as when loading animations, although it is possible in most cases. The specific principle is initially speculated to be related to the rendering mechanism of the browser. After searching for a long time, I first found its processing method in the source code of Layui. I directly set the delay of the first setTimeout to 50ms, although I don’t know why this value is set. But when I actually tested it (I don’t think it’s 100% guaranteed to prevent style merges in all cases), I looked up some other sources and found the best solution on StackOverflow:

window.requestAnimationFrame(function () {
  document.getElementById("two").setAttribute("style"."height: 0px");
  window.requestAnimationFrame(function () {
    document.getElementById("two").setAttribute("style"."height: 200px");
  });
});
Copy the code

I used the requestAnimationFrame method twice to execute the styled code in sequence. After testing it on multiple browsers, I found that this method was much more stable and reliable than setTimeout, so I ended up using this method to deal with the automatic merging of styles in browsers. See the answer in this StackOverflow post for some specific reasons

Another advantage of using this method for rotation animation is that it is very beneficial to expand functions. For example, to change the slide sliding effect of the rotation panel to fade in and out effect, you only need to change the style of the corresponding class from transfrom change to opacity change. In fact, Some mature UI frameworks are also implemented with similar ideas (if you use the previous animation algorithm, you can only write a separate animation module, very unfriendly to the maintenance and expansion of the later period)

If you have a more perfect way to avoid style merging, or a more elegant and efficient rotation algorithm, please share with me

This is all about the pit filling of paging and casting components. The basic functions have been realized, and the effect is not too big a problem after the initial test. Some more complex functions will be added in the subsequent update

Update (2020-04-12, fixes a “small” issue)

When capturing the dynamic diagram of the Carousel component, we found a hidden Bug:

WTF? ! Why is there a very thin line between two Carousel-panels? It is clear that they are moved synchronously, which should not be the case. Then I wrote a demo to verify this problem, and found the same problem as expected:

The rendering Bug appears only in Chrome. Edge, Firefox, and Safari are all normal, so this is a browser Bug. I also raised a related issue on StackOverflow and segmentFault:

when-two-elements-use-transform-to-move-at-the-same-time-why-there-is-a-gap-in

When two elements are animated using transform simultaneously, there is a gap between them. Is this a browser BUG

This road is blocked, so I have to find another way. After thinking for a long time, I found an animation scheme to avoid this Bug:

setCarouselPanel ($from, $to, direction) {
  this.isAnimate = true
  this.resetCarouselPanel($to, direction)
  this.moveCarouselPanel(direction, $from, $to)
}

resetCarouselPanel ($to, direction) {
  const type = direction === 'left' ? 'next' : 'prev'
  $to.setAttribute('class'.`carousel-panel ${type}`)
  this.$panelContainer.classList.add(`${direction}`)
}

moveCarouselPanel (direction, $from, $to) {
  setTimeout((a)= >{$from.setAttribute('class'.'carousel-panel')
    $to.setAttribute('class'.'carousel-panel active')
    this.$panelContainer.classList.remove(`${direction}`)
    this.isAnimate = false
  }, this.duration)
}
Copy the code

The code does the move directly on the parent container of the Carousel-Panel. This not only solves the rendering problem completely, but also eliminates the requestAnimationFrame operation, which is perfect

I tested it for many times afterwards, and basically found no other problems. Now the round broadcast feels like a relatively “stable” version. If you find other problems, please feel free to send me an issue

The progress of

  • Tabs – TAB
  • Collapse- Collapsible panel
  • Pager – paging
  • Carousel – version
  • Calendar Calendar –
  • Tree- A Tree control
  • Unit testing
  • TypeScript refactoring

The basic functions of Tabs, Collapse, Pager and Carousel have been completed, and the tutorials for Tabs, Collapse and the following updated components will be released soon

This set of tutorials will be first updated on my blog, welcome to my personal website and Github