Make writing a habit together! This is the sixth day of my participation in the “Gold Digging Day New Plan ยท April More text Challenge”. Click here for more details.

Said in the previous

๐ŸŽˆ has been working on his personal blog website recently. There is a module that needs the waterfall flow picture ๐Ÿ–ผ to show, so I packaged it into a component, which can be imported and used later. The specific effects are as follows: ๐Ÿ‘‡ :

What is waterfall flow

Waterfall flow, also known as waterfall flow layout. A popular web page layout that visually presents a jagged, multi-column layout that loads blocks of data and appends them to the current end as the page scrollbars scroll down. Pinterest was one of the first sites to use this layout, and it became popular in China. Most of the fresh stations in China are basically in this style.

Implementation approach

There are two ways to realize waterfall flow layout: one is to fix the width of the picture and the width of the container, and dynamically calculate the column number of the display picture; The other is to fix the number of columns displayed in the image and dynamically calculate the width of the image by the width of the container. Let’s take a look at the two different implementations.

First, fixed picture width

Suppose we now have a container and some images, how do we use the waterfall layout to display the images in the container?

1. Calculate the number of image columns that the container can display

ImgWidth = imgWidth; contentWidth = contentWidth; ParseInt (contentWidth/imgWidth) or math.floor, where the maximum number of columns should be the container width divided by the image width, as shown below:

There’s not enough space left for one more image, so round down.

Fill each column with images

Once we know the number of columns, we can start filling each column with an image, as shown below. The container can be divided into five columns.

Next fill the container with the image below

As shown in the animation above, the final result is as follows (numbers represent the order of images) :

Every time an image is inserted, the column with the lowest current height is always selected. If the sixth image should be inserted below the second column of the second column, how can this be done with the code?

3. Code implementation

html
<template>
    <div id="img-content" class="img-content">
        <img
            class="img"
            :style="'width:' + imgWidth + 'px; '"
            v-for="(item, index) in imgList"
            :key="'img' + index"
            :src="item"
        />
    </div>
</template>
Copy the code
CSS
<style lang="scss" scoped>
.img-content {
    height: 30vh;
    width: 320px;
    position: relative;
    .img {
        position: absolute;
        vertical-align: top;
        margin: 3px;
    }
}
</style>
Copy the code
JavaScript
Parameter configuration

The parameters we need to receive are:

  • (1) imgList: list of pictures displayed;
  • (2) imgWidth: the size of the image;
  • (3) imgMargin: the space between images.
props: {
    imgList: {
        type: Array.default: () = >[]},imgWidth: {
        type: Number.default: 100
    },
    imgMargin: {
        type: Number.default: 3}},Copy the code
Image location calculation
  • (1) heightArr: Height array

We can define a heightArr variable to store the current usage height of each column.

  • (2) minIndex: index of the column with the lowest current height

When inserting, you need to find the column with the lowest current height and insert the image into that column.

  • (3) Assign top and left to the picture
// Height The smallest height of the array
const minHeight = Math.min(... heightArr);// Index of the smallest height of the height array
const minIndex = heightArr.indexOf(minHeight);
item.style.top = minHeight + "px";
item.style.left = minIndex * imgWidth + "px";
Copy the code
  • (4) Complete code
methods: {
    waterfallHandler() {
        const imgWidth = this.imgWidth + this.imgMargin * 2;
        const contentW = document.getElementById("img-content").offsetWidth;
        // Get the column number of the image
        const column = parseInt(contentW / imgWidth);
        // Height array
        const heightArr = new Array(column).fill(0);
        const imgList = document.getElementsByClassName("img");
        for (let i = 0; i < imgList.length; i++) {
            const item = imgList[i];
            // Height of the current element
            const itemHeight = item.offsetHeight;
            // Height The smallest height of the array
            const minHeight = Math.min(... heightArr);// Index of the smallest height of the height array
            const minIndex = heightArr.indexOf(minHeight);
            item.style.top = minHeight + "px";
            item.style.left = minIndex * imgWidth + "px"; heightArr[minIndex] += itemHeight; }}}Copy the code
  • (5) Monitor page size changes

Recalculate when the page size changes.

window.onresize = () = > {
    return (() = > {
        this.waterfallHandler(); }) (); };Copy the code

4. Complete code

<template>
    <div id="img-content" class="img-content">
        <img
            class="img"
            :style="'width:' + imgWidth + 'px; '"
            v-for="(item, index) in imgList"
            :key="'img' + index"
            :src="item"
        />
    </div>
</template>

<script>
export default {
    name: "JWaterfall".props: {
        imgList: {
            type: Array.default: () = >[]},imgWidth: {
            type: Number.default: 100
        },
        imgMargin: {
            type: Number.default: 3}},mounted() {
        window.onresize = () = > {
            return (() = > {
                this.waterfallHandler(); }) (); };this.waterfallHandler();
    },
    methods: {
        waterfallHandler() {
            const imgWidth = this.imgWidth + this.imgMargin * 2;
            const contentW = document.getElementById("img-content").offsetWidth;
            // Get the column number of the image
            const column = parseInt(contentW / imgWidth);
            // Height array
            const heightArr = new Array(column).fill(0);
            const imgList = document.getElementsByClassName("img");
            for (let i = 0; i < imgList.length; i++) {
                const item = imgList[i];
                // Height of the current element
                const itemHeight = item.offsetHeight;
                // Height The smallest height of the array
                const minHeight = Math.min(... heightArr);// Index of the smallest height of the height array
                const minIndex = heightArr.indexOf(minHeight);
                item.style.top = minHeight + "px";
                item.style.left = minIndex * imgWidth + "px"; heightArr[minIndex] += itemHeight; }}}};</script>

<style lang="scss" scoped>
.img-content {
    height: 30vh;
    width: 320px;
    position: relative;
    .img {
        position: absolute;
        vertical-align: top;
        margin: 3px; }}</style>
Copy the code

Two, the number of fixed picture columns

1. Calculate the width of each column

Let the number of columns displayed becolumn, the container width iscontentWidth, then the width of each column of the container should be:parseInt(contentWidth / column)Or use math.floor, where the width of each image should beparseInt(contentWidth / column), as shown below:

Fill each column with images

As shown above, each LI represents a column that you floatfloat:left;Therefore, this method does not need to calculate the specific coordinate value of the picture, only need to insert the picture into the lowest height column, the specific implementation steps are similar to the previous method, but also need to constantly insert from the lowest height column, I will not repeat again.

3. Code implementation

html
<template>
    <div id="j-water-fall-content"></div>
</template>
Copy the code
CSS
<style lang="scss" scoped>
#j-water-fall-content {
    margin: 0;
    padding: 0;
}
</style>
Copy the code
JavaScript
Parameter configuration

The parameters we need to receive are:

  • (1) imgList: list of pictures displayed;
  • (2) column: the number of displayed columns;
  • (3) imgMargin: the space between images.
props: {
    imgList: {
        type: Array.default: () = >[]},column: {
        type: Number.default: 4
    },
    imgMargin: {
        type: Number.default: 0.5}},Copy the code
Dynamically inserting images
createImg() {
    const ul = document.getElementById("j-water-fall-content");
    let trueWidth = Math.floor(
        (100 - this.column * this.imgMargin * 2) / this.column
    );
    for (let i = 0; i < this.column; i++) {
        let li = document.createElement("li");
        li.style.listStyle = "none";
        li.style.float = "left";
        li.style.width = `${trueWidth}% `;
        li.style.margin = ` 0The ${this.imgMargin}% `;
        ul.appendChild(li);
        this.arr.push(li);
        this.minHeight.push(0);
    }
    let img = new Image();
    img.num = 0;
    img.src = this.imgList[img.num];
    img.style.width = "100%";
    // After the image is loaded
    img.onload = this.loadHandler;
},
loadHandler(that) {
    const img = that.path[0];
    const minHeight = this.minHeight;
    const arr = this.arr;
    // The minimum of the height array
    const min = Math.min.apply(null, minHeight);
    // The minimum index of the height array
    const minIndex = minHeight.indexOf(min);
    // Clone the image
    const im = img.cloneNode(true);
    // Put the image in the container corresponding to the minimum index
    arr[minIndex].appendChild(im);
    // Update the height of the container for the minimum index
    minHeight[minIndex] += im.height;
    img.num++;
    img.src = this.imgList[img.num];
}
Copy the code

4. Complete code

<template>
    <div id="j-water-fall-content"></div>
</template>

<script>
export default {
    name: "JWaterfall".props: {
        imgList: {
            type: Array.default: () = >[]},column: {
            type: Number.default: 4
        },
        imgMargin: {
            type: Number.default: 0.5}},data() {
        return {
            minHeight: [].arr: []}; },mounted() {
        this.createImg();
    },
    methods: {
        createImg() {
            const ul = document.getElementById("j-water-fall-content");
            let trueWidth = Math.floor(
                (100 - this.column * this.imgMargin * 2) / this.column
            );
            for (let i = 0; i < this.column; i++) {
                let li = document.createElement("li");
                li.style.listStyle = "none";
                li.style.float = "left";
                li.style.width = `${trueWidth}% `;
                li.style.margin = ` 0The ${this.imgMargin}% `;
                ul.appendChild(li);
                this.arr.push(li);
                this.minHeight.push(0);
            }
            let img = new Image();
            img.num = 0;
            img.src = this.imgList[img.num];
            img.style.width = "100%";
            // After the image is loaded
            img.onload = this.loadHandler;
        },
        loadHandler(that) {
            const img = that.path[0];
            const minHeight = this.minHeight;
            const arr = this.arr;
            // The minimum of the height array
            const min = Math.min.apply(null, minHeight);
            // The minimum index of the height array
            const minIndex = minHeight.indexOf(min);
            // Clone the image
            const im = img.cloneNode(true);
            // Put the image in the container corresponding to the minimum index
            arr[minIndex].appendChild(im);
            // Update the height of the container for the minimum index
            minHeight[minIndex] += im.height;
            img.num++;
            img.src = this.imgList[img.num]; }}};</script>

<style lang="scss" scoped>
#j-water-fall-content {
    margin: 0;
    padding: 0;
}
</style>
Copy the code

Three, contrast

In my opinion, the implementation of method 2 is better than method 1, because method 1 requires an appropriate container width and image width, otherwise there will be a certain space of white space, such a display effect is not beautiful, as shown below:

Because the width of the container is too large to the width of the picture, the right side will be left blank, which will give a poor visual effect. Method 2 can be used to well fill the space of the container, as shown in the following figure:

Package as a component

To make it easier for me to reuse it later, I put it into one of my component libraries so that I can reference it directly when I need it.

use

As shown below, use it in your blogBlog Address:Jyeontu. Xyz/JYeontuBlog…

Component library address

  • The document

Jvuewheel: jyeontu. Xyz/jvuewheel / #…

  • The source code

Gitee: gitee.com/zheng_yongt…

Past wonderful

In order to learn mo from Yu, I actually developed such a plug-in

Programmer romance – lovers daily small program

JavaScript bidirectional linked list implementation LRU cache algorithm

JavaScript bidirectional linked list implementation LFU cache algorithm

JavaScript implements prefix trees

Vue simple implementation of the word cloud component

Vue + ECharts realizes drilling linkage of map provinces in China

Pure CSS levitates hints and encapsulates them into VUE components

Said in the back

๐ŸŽ‰ this is JYeontu, now a front-end engineer, who can brush algorithm problems in spare time. Usually, he likes playing badminton ๐Ÿธ and writing some things. He records ๐Ÿ“‹ for himself, but also hopes that he can help us a little. Thank you for your support, and we’ll see you at ๐Ÿ™Œ.