Article reference: juejin.cn/post/701465…
Method 1: column implementation
- Column-count: defines how many columns the screen is divided into
- Column-gap: Defines the distance between columns
body{
margin: 0;
list-style: none;
}
#box{
margin: 40px;
column-count: 5;
column-gap: 20px;
}
#box > li > img{
width: 100%;
height: 100%;
}
Copy the code
Advantages:
- Implement a simple
- Image auto-fill regardless of image loading status
Disadvantages:
- Compatibility is poor
- The arrangement is always up and down, left and right, out of control
Method 2: Flex layout implementation
body{
margin: 0;
list-style: none;
}
#box{
display: flex;
flex-flow: column wrap;
height: 2000px;
}
#box > li{
margin: 10px;
width: calc(100% / 4 - 20px);
}
#box > li > img{
width: 100%;
height: 100%;
}
Copy the code
Advantages:
- Relatively simple implementation
- Image auto-fill regardless of image loading status
- The order can be changed to some extent
Disadvantages:
- The height is fixed
You can use the above methods in small projects, but the actual development is still done in JS
Method three: JS implementation
export default class Waterfall {
/* * $el: parent container * width: width of each image * items: all child elements * H: store height of each column * flag: virtual DOM node set * */
constructor(options) {
this.$el = null
this.count = 4
this.gap = 10
Object.assign(this, options)
this.width = 0
this.items = []
this.H = []
this.flag = null
this.init()
console.log('ok')}init() {
this.items = Array.from(this.$el.children)
this.reset()
this.render()
}
reset() {
this.flag = document.createDocumentFragment() // Create a blank document
this.width = this.$el.clientWidth / this.count
this.H = new Array(this.count).fill(0)
this.$el.innerHTML = ""
}
render() {
const {width, items, flag, H, gap} = this
items.forEach(item= > {
item.style.width = width + 'px'
item.style.position = 'absolute'
let img = item.querySelector('img')
/* The image is loaded */
if (img.complete) {
/* Get the minimum height of each column */
let tag = H.indexOf(Math.min(... H)); item.style.left = tag * (width + gap) +'px'
item.style.top = H[tag] + 'px'
img.style.width = '100%'
img.style.height = '100%'
H[tag] += img.height * width / img.width + gap
flag.appendChild(item)
} else {
img.addEventListener('load'.() = > {
let tag = H.indexOf(Math.min(... H)) item.style.left = tag * (width + gap) +'px'
item.style.top = H[tag] + 'px'
img.style.width = '100%'
img.style.height = '100%'
H[tag] += img.height * width / img.width + gap
flag.appendChild(item)
this.$el.append(flag)
})
}
})
this.$el.append(flag)
}
};
Copy the code
Implementation principle:
- Initialize, calculate the column width, using H as the column height memory. The child elements are then evaluated and the contents of the parent container are cleared.
- Iterate over the child elements, set them to absolute positioning, set their column width, and then listen to see if the image under them has loaded.
- If it’s loaded, then it calculates where it should be, and the core of the waterfall stream is which column has the smallest height and it sets up a new image on that column. Of course, its relative height and spacing are also calculated, and the height is stored on the current H.
- Update the virtual node to the parent container every time the image is loaded.
Advantages:
- Flexible control, arbitrary expansion
- It can also be loaded indefinitely without worrying too much about compatibility
- Many animations can also be added to enhance the user experience
Disadvantages:
- Relatively complex implementation
- Image filling needs to consider the image loading state
- Performance is worse than pure CSS implementations