Waterfall flow layout is very common in the business, and there are many solutions. Today, I will introduce a highly automatic calculation based on DOM

Look at the renderings first

Our list is composed of text and images, in general, the picture is to determine the height of the temporary load (even if it’s not clear we can then determine), the text content requirement is a must here all display, don’t do cut processing, so there may be text, the circumstances of less card layout at this time will be relatively reduced

We split the list into left and right columns for separate cards

<template> <div class="WaterfallList"> <! <div class=" waterfalllist-left "> <div ref="left"> <div class="card" v-for="(item, $index) in left" :key="$index"> <div class="pic" :style="{ height: item.height + 'px', lineHeight: item.height + 'px', }" > {{ item.index }} </div> <div class="title">{{ item.title }}</div> </div> </div> </div> <! <div class=" waterfalllist-right "> <div ref="right"> <div class="card" v-for="(item, $index) in right" :key="$index"> <div class="pic" :style="{ height: item.height + 'px', lineHeight: item.height + 'px', }" > {{ item.index }} </div> <div class="title">{{ item.title }}</div> </div> </div> </div> </div> </template> <script> export default { props: { list: Array, }, data() { return { left: [], right: [], }; }}; </script>Copy the code

Therefore, when rendering we need to get the height of the current left and right list and stuff the new data into the smaller container

const newItem; // The next data to insert

let leftHeight = this.$refs.left.getBoundingClientRect().height;
let rightHeight = this.$refs.right.getBoundingClientRect().height;
let index = this.left.length + this.right.length;

if (leftHeight < rightHeight) {
  this.left.push(newItem);
} else {
  this.right.push(newItem);
}
Copy the code

Due to the use of vUE framework, dom does not always make new changes immediately after every data change, so it may cause that the code for obtaining height above cannot obtain the one after rendering is completed. Therefore, we need to wait for DOM to complete rendering, which is achieved by the following code

await this.$nextTick()
Copy the code

Thus, every time the list changes, we need to push the new content into our left and right arrays as needed

epxort default {
  watch: {
    list() {
      this.update(); }},mounted() {
    this.update();
  },

  methods: {
    async update() {
      while (this.left.length + this.right.length ! = =this.list.length) {
        let leftHeight = this.$refs.left.getBoundingClientRect().height;
        let rightHeight = this.$refs.right.getBoundingClientRect().height;
        let index = this.left.length + this.right.length;

        if (leftHeight < rightHeight) {
          this.left.push(this.list[index]);
        } else {
          this.right.push(this.list[index]);
        }

        await this.$nextTick(); }}}},Copy the code

Considering that the update method will be called many times if the watch is fired frequently, and the previous update work is not always finished, we add a lock to ensure that it does not reenter

if (this.called) {
    return;
}

this.called = true;
while (this.left.length + this.right.length ! = =this.list.length) {
    let leftHeight = this.$refs.left.getBoundingClientRect().height;
    let rightHeight = this.$refs.right.getBoundingClientRect().height;
    let index = this.left.length + this.right.length;

    if (leftHeight < rightHeight) {
      this.left.push(this.list[index]);
    } else {
      this.right.push(this.list[index]);
    }

    await this.$nextTick();
}
this.called = false;
Copy the code

WaterfallList -calc-by-$nexttick-codesandBox

So the question is, what do we do if the list gets deleted? Can we

  1. First, filter out elements that no longer exist in the list from left and right
  2. After filtering, the left and right heights may vary greatly, so we do a loop, taking the last item from the larger column and placing it in the smaller column until the larger column is no longer larger than the smaller column (height changes as we move).
  3. At this point, continue the above loop and insert the new content into the left and right as appropriate