Waterfall flow is a common layout method, which can be implemented in many ways, such as directly dividing into two columns, and then controlling the addition of elements in the left and right columns; Another way is to place both sides in absolute position. The waterfall flow described in this article is different from the normal waterfall flow because it may be truncated in the middle:

For the layout of the above, if forced into two columns do layout is not well suited to, so I used the absolute positioning way for layout, because the flow of elements in the waterfall height is not fixed, therefore I have to think of some way to get to the height of each element, and then determine the element is put a whole line, and the left or the right.

First let’s look at the implementation of the template part:

<view class="container" style="height:{{height}}px;" > <view wx:for="{{list}}" wx:key="index" style="{{item.style}}" class="wrapper"> <abstract item="{{item}}"/> </view> </view> <view wx:if="{{tmp}}" class="computed-zone"> <view class="wrapper"> <abstract item="{{tmp}}"/> </view> </view>Copy the code

The template is simple: a container, then loop through the array, rendering a bunch of Wrapper containers horizontally.

The Wrapper container is an absolutely positioned wrapper element, and inside the Wrapper container is the component that needs to be actually rendered. For flexibility, I set the render component as a virtual node, and specify the custom component that needs to be actually rendered when using the component.

Because the Wrapper elements are absolutely positioned, we need to manually maintain the height of the entire Container.

So the question here is, how do we get the height of the elements inside? Computed – Zone in the template is designed to solve this problem. Before putting elements into the array, we render elements in computed- Zone, and then use the WXML API to get the actual render size of the elements. This will give us an idea of the actual render width of the element.

Now that we have the render size information for each element, we need to determine whether the element takes up the entire line or half of it:

  1. If the render width of the element is the same as that of the container, then you can tell that the element is all over the line and needs to be wrapped around the containerwrapperSet to the width of a full line;
  2. If the condition 1 is not met, then based on the total height of the left and right elements, will bewrapperOn the left or the right.

We need to do a little bit of logic to calculate the offset for the Wrapper, whether to put it to the left or right, or to fill the entire line.

{
	// Make setData Promise easier to use
	$setData(data) {
		return new Promise(resolve= > {
			this.setData(data, () = > {
				resolve();
			});
		});
	},
	// Get the render size of the element
	getRect(item) {
		return this.$setData({
			tmp: item,
		}).then(() = > {
			return new Promise((resolve, reject) = > {
				const query = this.createSelectorQuery(); // Use this instead of the wx prefix
				query.select('.computed-zone .wrapper').boundingClientRect();
				query.exec(ret= > {
					if (ret[0]) {
						resolve(ret[0]);
					} else {
						reject('not found dom! '); }}); }); }); },// Add elements for internal use
	addItem(item) {
		let tick = this.tick;
		return this.getRect(item).then(rect= > {
			if(tick ! = =this.tick) {
				return Promise.reject('tick');
			}
			const { margin } = this.data;
			let { height, width } = rect;
			const windowWidth = this.sysInfo.windowWidth;
			let [ leftTotal, rightTotal ] = this.height; // leftTotal left column height, rightTotal right column height,
			let marginPx = this.sysInfo.getPx(margin);
			let style = ' ';

			if (Math.abs(width - windowWidth) < 3) {
				// Fill up the screen width
				style = `left:0; top:The ${Math.max(leftTotal, rightTotal) }px; width:100%; `;
				leftTotal = rightTotal = Math.max(leftTotal + height, rightTotal + height);
			} else if (rightTotal < leftTotal) {
				// Put it on the right side
				style = `right:${ marginPx }px; top:${ rightTotal }px; `;
				rightTotal += height;
			} else {
				// Put it on the left
				style = `left:${ marginPx }px; top:${ leftTotal }px; `;
				leftTotal += height;
			}

			const { list = [] } = this.data;
			const targetKey = `list[${list.length}] `; // Use direct manipulation of array subscripts to trigger array changes, which greatly improves performance
			this.height = [leftTotal, rightTotal]; // Record the latest height on the left and right sides
			return this.$setData({
				[targetKey]: {
					data: item,
					style,
				},
				height: Math.max(leftTotal, rightTotal),
			});
		});
	},
	// Create a Promise queue to ensure that the sequence is consistent
	add(item) {
		let pending = this.pending || Promise.resolve();
		return this.pending = pending.then(() = > {
			return this.addItem(item);
		}).catch(err= > {
			console.error(err);
			this.pending = null;
			throw err;
		});
	},
	clear() {
		this.tick = tick++;
		this.height = [0.0];
		this.pending = null;
		this.setData({
			list: [].height: 0}); }},Copy the code

Instead of rendering an element directly by assigning an array to it, we use the component instance method Add (item), because I implement the queue, so WE can just loop through add. If you care about the state, check to see if the add operation on the last element is complete.

Waterfall flow achieved in this way is relatively flexible, but the performance cost is not low, need to obtain the actual render size of elements one by one, if you want to support window resize, the cost is terrible.

For students who need to see the details of the code, I put the actual demo on Github and wechat code snippets, students who need to have a try.

Based on the above model, it can also be optimized to render only the elements within the viewable area, which can greatly improve the performance of waterfall flow. I hope the students with time can improve it, I give a thumbs up ~o( ̄▽ ̄)d