I recently came across a need to create a two-column waterfall layout on my mobile phone, so I researched the problem for a note.

Generally speaking, this layout can be divided into two cases:

  1. The number of images is a certain, do not need to scroll to the bottom of the page, and then dynamically load the image, just need to arrange the image in several columns
  2. The number of images varies, and when the page hits the bottom, images need to be loaded remotely.

The former can be solved using CSS, while the latter needs JS to help.

The CSS method

1. CSS multi-column layout

When we show a certain number of images, we can preferentially use CSS solutions. One way to do this is with CSS multi-column layouts:


.photos{
  
  column-count: 3;
  column-gap: 10px;
}
Copy the code

The results are as follows:

Kjmjmq

Flex layout

The flex layout can do the same. The trick is to set flex-direction to Column; However, as opposed to a multi-column layout, you need to calculate an appropriate container height based on the number of columns in the waterfall flow, otherwise you may end up with an extra row. If you see four columns in the following demo, don’t doubt that I calculated the container height incorrectly…

waterflow-flex

Js solution

When images need to be inserted dynamically, the above two methods are not suitable because they are essentially arranging images vertically. When inserting images dynamically, we usually want the image to be inserted horizontally into the container. This is where JS comes in. First, let’s look at the relationship between waterfall flow and the backpack problem.

The basic idea of waterfall flow is to place a bunch of images in a number of columns, with the height of each column being uniform without too much difference. If we were to divide into two columns, then the problem would be to pick m images out of n, and make the total height of the m images as close as possible to 1/2 of the total height of the n images. So it becomes a backpack problem

01 Backpack Problem

What is the backpack problem is not to expand here, to put it plainly is to decompose a complex problem into several simple problems, the big guys speak better than ME, there are various language versions of the implementation on the Internet, students who do not understand can check the link above. I’m just going to put a function here


function dp(ws, vs, limit) {
	let len = ws.length;
    let tables = new Array(len).fill().map(x= > [])
	tables[-1] = new Array(limit + 1).fill(0);

    for(let i = 0; i < len; i++) {
        for (let w = 0; w <= limit; w++) {
            if (ws[i] > w) {
                tables[i][w] = tables[i-1][w]
            } else {
                tables[i][w] = Math.max(tables[i-1][w], tables[i-1][w-ws[i]] + vs[i])
            }
        }
    }
    // Backtrack to get what should be selected
    let max = limit;
    let selected = [];
    for (let idx = len - 1; idx >= 0; idx--) {
        if (ws[idx] <= max) {
            let isSelected = tables[idx-1][max] < tables[idx-1][max-ws[idx]] + vs[idx]
            if(isSelected) { selected.push(idx); max = max - ws[idx]; }}}return selected;
}
Copy the code

With this solution, it is not difficult to write a waterfall flow layout. The idea is that if we want to make a three-column waterfall layout, we can constantly pick a group of images from the image array, making the height of the images close to 1/3 of the total height, and end up with three groups of images. Here is a code snippet

	  // colCount indicates how many columns to generate
      while(colCount--) {
          // Get the index of the selected photos
          let idxs = dp(photoHeights, photoHeights, aver)
          // Get the selected group of images
          let photoCol = photos.filter((p,idx) = > idxs.includes(idx)) 
          this.cols.push(photoCol)
          photoHeights.forEach((v,i) = > {
          if (idxs.includes(i)) {
            photoHeights[i] = null}})}Copy the code

The following demo is implemented in this way, you can drag the slider below to change the number of columns, observe the gap at the bottom. When using the knapsack algorithm to solve the waterfall flow problem, one thing we need to be careful about is converting the image height to an integer.

waterflow-js

Refer to the article: www.cnblogs.com/ainyi/p/876… www.cnblogs.com/AndyJee/p/4… Segmentfault.com/a/119000001…