Project upgrade & pit filling
instructions
I decided to restart the wheel project after a gap of more than half a year for several reasons:
- The old project structure was too bloated
- There are some big problems in the implementation of components
- 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:
- Use JavaScript to calculate the ordinal number of child elements
- use
position: absolute + left
ortransform
Controls 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:
- Frequent DOM operations result in low performance
- Flicker in some browser renderings
- 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