Introduction:

In the field of front-end, we often encounter the development of waterfall flow layout. Recently, I sorted out the relevant usage scenarios and solutions, including simple algorithm DP, front-end basic knowledge, and thinking of business scenarios.

What is waterfall flow layout

Waterfall layout, also known as waterfall layout, is a popular layout method for navigation. Unlike the traditional pagination display, which visually presents a jagged multi-column layout, it was first used by Pinterest.

Especially in the mobile terminal, the application of double row cascade flow more common, present each element to their own situation in present reasonable occupy a space, each element is high, wide or so, in turn, adjusting arrangement, finally occupy a minimum height of the screen, with unlimited load design, no matter from the user’s use of psychological considerations, display of beautiful, user experience into consideration, such as Waterfall flow is a pretty good layout.

Take the Waterfall stream of Tencent Classroom APP as an example:

Usage scenarios

According to the advantages and disadvantages of waterfall flow, it is not difficult to figure out when choosing waterfall flow is a reasonable choice:

  • Waterfall streams are a better choice when the content is mostly graphic. Pictures take up a lot of space, and the brain can understand them faster than words, so a lot of content can be scanned in a short time. Therefore, if it is displayed in pages, users must turn pages frequently, affecting the immersive experience. Waterfall stream can solve this problem.
  • Waterfall flow is a better choice when information is relatively independent of one another. If the information is relevant, the user must do a lot of backtracking to see before and after the information, whereas if the information is relatively independent, a waterfall stream can be used to allow the user to receive information from different places at the same time.
  • Waterfall flow is a better choice when information is relatively independent of one another. The intuitive impression of a waterfall stream is that simultaneous information is roughly the same as a user’s search, while the intuitive impression of a paginated display is that information higher up is considered to be a better match to the user’s search. Therefore, waterfall flow can be used when there is no obvious difference between information and search match.
  • Waterfall flow is a better choice when the user is not motivated. Paging is easier when users have specific information to look for, while waterfall streams can increase user stay time and unexpected gains when the purpose is weak.

Here is a summary of an article that cites waterfall flow as an effective way to guide users to use fragmented time to maximize retention and usage time.

How to achieve waterfall flow layout

Combined with previous conclusions, there are three ways to realize waterfall flow at present: multi-column, grid and Flexbox. Each implementation scheme has its own differences. I will not give you a specific explanation here. The Flexbox layout is recommended for compatibility and ease of use.

Generally speaking, the HTML structure is as follows :(take wechat applets as an example)

<view class="container"> <view class="column-container"> <template is="item-card" wx:key="{{item.id}}" wx:for="{{left}}"  data="{{... item}}" /> </view> <view class="column-container"> <template is="item-card" wx:key="{{item.id}}" wx:for="{{right}}" data="{{... item}}" /> </view> </view>Copy the code

In the code above, container represents the waterfall flow container, which is responsible for scrolling and triggering infinite loading; Column-container is a column container, and item-card is each item in it.

The CSS Settings are as follows:

.container { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-start; margin-top: 12px; > .column-container { flex: 1 1 0; margin: 4px; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; }}Copy the code

Flex sets the horizontal layout for waterfall flow containers and the vertical layout for column containers.

The corresponding data tuples are also divided into the following: couponList is the total data, left is the data allocated to the left column, and right is the data allocated to the right column. The specific optimization of the distribution mode is the focus of subsequent analysis. Here, the analysis is carried out in accordance with the following table.

Page({  
  data: {  
  couponList: [], 
   left: [],  
   right: [], 
 },
}
Copy the code

It’s only here that we really get to the point of this article: How to make a high-performance, high-experience H5 cascade flow?

Here we first choose a use scenario, technical implementation of Flexbox to achieve the layout, data loading requirements infinite scrolling down loading, can facilitate everyone to pay more attention to the specific business background, but also reduce the scope of optimization as the author to introduce, easy to tell. If there are other scenarios, we can discuss them in the comments section. We won’t cover them all here.

To be precise, the use scenarios of double-row waterfall flow can be divided into highly differentiated scenarios and sequentially differentiated scenarios based on whether the height of element cards is fixed and whether the order is strictly fixed, as follows:

Highly differentiated scenes:

  • Scenario A1: Each element is fixed in height;

  • Scenario A2: Each element is not fixed in height, but can be datatyped to estimate its own height as a percentage of the screen width;

  • A3 scene: element height is not fixed and cannot be predicted. The height can only be determined after rendering.

Sequential differentiation scenario :(combined with infinite loading as the premise)

  • Scenario B1: The relative order of elements is strictly the same

  • B2 scenario: The relative order of elements is broadly consistent

Let’s take a look at the specific scenarios and optimize the implementation details.

  • In A1 scenario, the data arrangement can be made into alternating left and right arrangement, which is also the simplest way.

    let i = 0; while (i <couponList.length) { left.push(couponList[i++]); if (i < couponList.length) { right.push(couponList[i++]); }}Copy the code
  • In scenario A2, elements need to be dynamically allocated to the left and right columns according to the current height difference. Simply speaking, the shorter column is allocated to the corresponding column. At the same time, this method can also meet the scenario of B1.

    Function computeRatioHeight (data) {// computs the current element's height as a percentage of the screenWidth const screenWidth = 375; Const itemHeight = data.height; return Math.ceil(screenWidth / itemHeight * 100); } function formatData(data) { let diff = 0; const left = []; const right = []; let i = 0; while(i < data.length) { if (diff <= 0) { left.push(data[i]); diff += computeRatioHeight(data[i]); } else { right.push(data[i]); diff -= computeRatioHeight(data[i]); } i++; } return { left, right } }Copy the code
  • A3 scenario is relatively difficult to handle, we are unable to predict the height of the real rendering after, so, in the case of uneven, we can’t scientific arranging, this situation is found in the waterfall flow scenarios images, due to their high image information is missing or inaccurate, wide need img tags unfold naturally, this case suggested that converts A2, For example, prefetch the true height and width of the image, of course, there is a performance cost to doing so.

    Function getImgInfo(url) {return new Promise((resolve, reject) => {const img = new Image(); // Change the image SRC img. SRC = img_url; Img.com plete) {resolve({width: img.width, height: img.height}); } else { img.onload = () => { resolve({ width: img.width, height: img.height }); }; }})}Copy the code

Advanced optimization

Error correction

In A2 scenario, the height of each card and can’t go as planned highly accurate rendering, especially used in the mobile terminal H5 Rem unit, the adaptation of different equipment type of scenario, the calculation precision is poor, rendered pixels error, will bring about calculation when the height difference error of certain error, on the basis of the infinite scroll, This error will continue to accumulate and eventually lead to the failure of the layout strategy. Therefore, the left and right height difference needs to be corrected after each data loading.

You can add a hidden anchor element with the height of 0px to the end of the left and right column container. After each rendering, you can get the offsetTop value of the anchor element and update the height difference between the left and right columns.

Here is the HTML structure:

<view class="container"> <view class="column-container"> <template is="item-card" wx:key="{{item.id}}" wx:for="{{left}}"  data="{{... item}}" /> <view class="hidden-archer" id="left-archer" /> </view> <view class="column-container"> <template is="item-card" wx:key="{{item.id}}" wx:for="{{right}}" data="{{... item}}" /> <view class="hidden-archer" id="right-archer" /> </view> </view>Copy the code

The following is the code for the update difference of the small program:

this.setData({ left: [...left, ...leftData], right: [...right, ...rightData], diffValue: DiffValue + nextDiff,}, () => {// Update the left/right interval const query = wx.createsElectorQuery (); Console. log(' calculate height difference '); query.select('#left-archer').boundingClientRect(); query.select('#right-archer').boundingClientRect(); const { diffValue } = this.data; query.exec((res) => { console.log(res[0].top - res[1].top, diffValue); this.setData({ diffValue: res[0].top - res[1].top, }); }); });Copy the code

LeftData and rightData are newly added permutation data, and diffValue is the height difference between left and right columns. It has been proved that diffValue and the height difference between left and right anchor points have errors (as shown in the figure below), which need to be corrected by this means.

The optimal arrangement is obtained by DP algorithm

In A2 scenario, adding elements to the column with the lowest height by calculating the height difference is actually not a perfect solution, because in extreme scenarios, for example, if the last element is too high, the height difference between the left and right sides of the bottom will be too large, even exceeding the height of a common element. On the one hand, the screen height is not properly used. On the other hand, huge height differences can also have a negative impact on the user experience.

To solve this problem, we introduce a simple DP algorithm to solve this problem. If known to be the height of the array elements, these elements can be calculated the true account of the highly – remember to total height H, if do not consider integral card features, to think two column containers into unicom two water column, then the element total height H / 2 is the best height, because it is hard to discern about arrangement of highly consistent, Therefore, obtaining the arrangement height closest to H / 2 is the optimal arrangement height, and then converted into the backpack problem is in the backpack of H / 2 capacity, how to place as much as possible to use its space volume of the problem, the following according to this idea to solve how to obtain the optimal problem.

resetLayoutByDp: function (couponList) { const { left, right, diffValue } = this.data; const heights = couponList.map(item => (item.height / item.width * 160 + 77)); const bagVolume = Math.round(heights.reduce((sum, curr) => sum + curr, diffValue) / 2); let dp = []; / /... Const rightIndex = DP [heights.leng-1][bagVolume].indexes; const nextDiff = heights.reduce((target, curr, index) => { if (rightIndex.indexOf(index) === -1) { target += heights[index]; } else { target -= heights[index]; } return target; }, 0); const rightData = rightIndex.map(item => couponList[item]); const leftData = couponList.reduce((target, curr, index) => { if (rightIndex.indexOf(index) === -1) { target.push(couponList[index]); } return target; } []); this.setData({ left: [...left, ...leftData], right: [...right, ...rightData], diffValue: DiffValue + nextDiff,}, () => {// Update the left/right interval const query = wx.createsElectorQuery (); Console. log(' calculate height difference '); query.select('#left-archer').boundingClientRect(); query.select('#right-archer').boundingClientRect(); const { diffValue } = this.data; query.exec((res) => { console.log(res[0].top - res[1].top, diffValue); this.setData({ diffValue: res[0].top - res[1].top, }); }); }); }Copy the code

Optimize the arrangement in the column container

In actual business scenarios, there are often requirements on the order, which is common in advertising and recommendation algorithms. Here, some front-end optimization can also be done. The method here is mainly sorting within the column container and replacing the same elements in different column containers to ensure that the elements with higher priority appear in the first place as much as possible.

The final result is shown below: