rendering

Come on, look, look, it’s a beautiful world out there,

The renderings show the effect of lazy loading on the waterfall flow layout

data

Photo data source zhang Xinxu’s blog

Let’s start with our image link format

All link is http://cued.xunlei.com/demos/publ/img/P_${name}. The JPG format, we just need to change the value of the name, when the name value is less than 10, 00 format is x, such as 002, 003, When you’re greater than 10, it’s like 023.

define

Waterfall layout is a popular way of page layout. Pinterest was the first website to use this layout. The picture width is fixed and the height is automatic, which produces a kind of uneven beauty.

The principle of

The principle is very simple, mainly divided into the following steps

Define the height array and column number

2. Iterate over the elements and push any less than the number of columns directly into the array

3. If the value is greater than the number of columns, get the smallest value in the height array and define the top and left values of the elements

4. Importantly update the height array by adding the minimum height to the height of the current element

Now that I know how it works, how do I write the code? Here is an example of the Web side, as follows

let heightArr = []
let col = 2
let allBox = document.querySelectorAll('.box') // Get all boxesfor(let i in allBox){
	
	letBoxWidth = allBox[0].offsetwidthlet boxHeight = allBox[i].offsetHeight
	if(I < col){heightArr. Push (boxHeight) // Add the first row height}else{// Perform layout operationsletMinHeight = mac.min. apply(null, heightArr) // Obtain the minimum heightletMinIndex = getIndex(heightArr, minHeight) // The minimum height subscript is either 0 or 1 allBox[I].style.position ='absolute'
		allBox[i].style.top = minHeight + 'px'
		allBox[i].style.width = minIndex * boxWidth + 'px'HeightArr [minIndex] += boxHeight // Update latest height}} // Obtain subscript getIndex(arr, val){for(i in arr){
		if(arr[i] == val) {
			return i
		}
	}
}

Copy the code

Above is the main logic to achieve waterfall flow, here is roughly written, next we look at the small program how to achieve.

implementation

DOM can be accessed and manipulated directly in web pages, which is easy to implement, and there are many jquery plug-ins available. We know that there is no DOM in the applet, so how do we implement it? Let’s just switch gears.

Here we implement the waterfall flow layout in three ways.

CSS

Using CSS3 is the easiest way to do this, so let’s start with something simple,

Use the column-count attribute to set the number of columns

Use wX-if to determine whether to render the image to the left or right

wxml

<view class='container'>
    <image src='{{item.url}}' wx:if="{{index % 2 != 0 }}" wx:for="{{list}}" mode='widthFix' wx:key="{{index}}"></image>
     <image src='{{item.url}}' wx:if="{{index % 2 == 0 }}" wx:for="{{list}}" mode='widthFix' wx:key="{{index}}"></image> 
</view> 
Copy the code

wxss

.container{
  column-count: 2;  /* Set the number of columns */ 
  column-gap:2rpx;
  padding-left: 8rpx;
}
image{
  width: 182px;
  box-shadow: 2px 2px 4px rgba(0, 0, 4); }Copy the code

Js to obtain the next data, here is not described.

Node information

Applets can use the WXML node information API to get information about elements.

wxml

<view class="container">
	  <view wx:for="{{group}}" style='position:{{item.position}}; top: {{item.top}}; left:{{item.left}}; width:{{width}}rpx; ' class='box box-{{index}}' wx:key="{{index}}">
 			<image  src='http://cued.xunlei.com/demos/publ/img/P_{{item.name}}.jpg' style=' height:{{height[index]}}px' bindload='load' data-index='{{index}}' class='image'></image>
	  </view>
</view> 
Copy the code

wxss

.container{
  position: relative;
  display: flow-root;
}
.box{
  float: left;
  display: flex;
  margin-left:5rpx;
  box-shadow: 2rpx 2rpx 5rpx rgba(0,0,0,.3);
  border: 1rpx solid #ccc;
  box-sizing: border-box;
  padding: 10px;
}
.box:nth-child(2){
  margin-left: 12rpx;
}
image{
  width: 100%;
}

Copy the code

js

Image links to http://cued.xunlei.com/demos/publ/img/P_${name}. JPG, only need to change the name

Let’s start with our data

// Create an array of length 30
const mockData = (a)= > {
  return Array.from(Array(30).keys()).map(item= > {
    if (item < 10) {
      return '00' + item
    } else {
      return '0' + item
    }
  })

}
// Expand to the data we need
const createGroup = (a)= > {
  let group = []
  let list = mockData()
  list.forEach(item= > {
    group.push({ name: item, position: 'static'.top: ' '.left: ' '})})return group
}

Copy the code

Then the waterfall flow layout, the main code is as follows

This.setdata ({height: [...this.data.height, e.diail.height]}) this.showimg () // call render function},showImg() {let height = this.data.height
    if(height.lenth ! = this.data.group.legth){// Make sure all images are loadedreturn
    }
    setTimeout(()=>{// Async wx.createsElectorQuery ().selectAll()'.box').boundingClientRect((ret) => {
        let cols = 2
        var group = this.data.group
        var heightArr = [];
        for (var i = 0; i < ret.length; i++) {
          var boxHeight = height[i]
          if (i < cols) {
            heightArr.push(boxHeight + 25)
          } else {
            var minBoxHeight = Math.min.apply(null, heightArr);
            var minBoxIndex = getMinBoxIndex(minBoxHeight, heightArr);
            group[i].position = 'absolute'
            group[i].top = `${minBoxHeight}px`
            group[i].left = minBoxIndex * this.data.width / 2 + 'px'
            group[i].left = minBoxIndex == 0 ?  minBoxIndex * this.data.width / 2 + 'px' : minBoxIndex * this.data.width / 2 + 5 + 'px'
            heightArr[minBoxIndex] += (boxHeight + 25)
          }
        }

        this.setData({
          group
        })
        wx.hideLoading()

      }).exec()
    }, 200)
    
  }
Copy the code

You can see that the implementation logic is similar to the above, except that we are modifying the data, after all, the applets are data-driven.

Here we mainly listen to the bindLoad event of the image component to obtain the height of each image. Only after obtaining the height can we carry out the layout. Most of the time is also used to load the image. Of course we can. We use Node to wrap the data.

The backend processes the data

As mentioned above, getting the height of the image inside the applet is a thankless task. We use Node to get the height of the image and then wrap it up for the applet to use.

  • Use Request for requests
  • Get the height of the image using image-size
  • Finally, the data is written to a file and a service provisioning interface is started

Here is the main problem encountered

The image-size Buffer must be a Buffer. The image-size Buffer must be a Buffer. The image-size Buffer must be a Buffer. Set encoding to NULL in the request

2. We have climbed 100 pictures here. How can we guarantee that we have climbed all of them? We could write it like this

Promise.all(list.map (item => getImgData(item))) // getImgData is a function that gets pictures and returns a PromiseCopy the code

3, if the request for a few times, found some pictures can not be obtained, error, what’s going on, after all, they did anti-crawling, congratulations you won the lottery, change the IP to try again (you can put the code on the server, or change wi-fi), in fact, we only need to climb once on the line, generated file also climb why ah.

The full code can be found at Github

Back to the applet, the interface returns the following data

Now you can see that each image has a height, so let’s implement waterfall layout. Wait, let’s do lazy loading of waterfall layout, learn more about lazy loading of applets.

How do you do that? There are two main steps

1. Layout the element waterfall flow

2. Create IntersectionObserver for lazy loading

Let’s start with our layout

wxml

<view class='container'>
  <view class='pic pic-{{index}}' wx:for="{{list}}" style="height:{{item.height}}px; left:{{item.left}}; top:{{item.top}}; position:{{item.position}}" wx:key="{{item.index}}">
    <image src='{{item.url}}' wx:if="{{item.show}}"></image>
    <view class='default' wx:if="{{! item.show}}"></view>
  </view>
</view>

Copy the code

We used wx-if to check whether the image was loaded or not using the show field.

Use a View component to hold the space and change the show field to display the image

js

Let’s use two for loops to do the layout first

let cols = 2
    let list = this.data.list
    let heightArr = [];


    for(let i in list){
      var boxHeight = list[i].height
      if (i < cols) {
        heightArr.push(boxHeight + 5)
      } else {
        var minBoxHeight = Math.min.apply(null, heightArr);
        var minBoxIndex = getMinBoxIndex(minBoxHeight, heightArr);
        list[i].position = 'absolute'
        list[i].top = `${minBoxHeight}px`
        list[i].left = minBoxIndex * 182 + 'px'
        list[i].left = minBoxIndex == 0 ? minBoxIndex * 182 + 'px' : minBoxIndex * 182 + 4 + 'px'
        heightArr[minBoxIndex] += (boxHeight + 5)
      }
    }
    this.setData({
      list
    })

Copy the code

After the layout, IntersectionObserver is created to dynamically judge the display of image nodes

for (let i in list) {
      
	wx.createIntersectionObserver().relativeToViewport({ bottom: 20 }).observe('.pic-' + i, (ret) => {
        if (ret.intersectionRatio > 0) {
          list[i].show = true
        }
        this.setData({
          list
        })
 })
}

Copy the code

The last

We completed the waterfall flow layout of the applet in three ways, plus lazy loading based on waterfall flow. You can find that the use of CSS is the most simple, although the small program can not operate DOM, but we changed the data and change the DOM, the concept of change, small program development is still very cool.

And finally, have a great weekend, everybody.

github