1. Idea analysis and effect diagram
Use vue to achieve a waterfall streaming effect, load network pictures, with drop-down refresh and pull-up load more features.Then, for the realization of these effects, put forward ideas:
- Labels are appended to show the effect in turn according to the order in which the data is loaded.
- Choose which way to achieve the waterfall flow, here choose the absolute positioning method;
- Key Issues: Because each picture has a different width and height, the waterfall flow requires all pictures to have the same width and height to scale with equal ratio of width.And because the loading of pictures is asynchronous delay.Without knowing the height of the picture, the item box in which each picture is located is not positioned absolutely.So getting the height of all the pictures before rendering the page is the key to solving the problem!Here you choose to use the Image class in JS to get the width and height of the picture in advance by pre-loading the picture, and a temporary variable to calculate if all the picture heights have been obtained.When all the picture heights are taken, the page is rendered.
- After rendering the page, get the boxes where all the pictures are located, cycle through the height of the boxes, and start setting the absolute positioning of each box item.
- Flashing occurs when a page is rendered.How can I solve this problem?An animation style is used here.But it still feels a little flickering the first time it loads.
- Then there's the drop-down refresh and pull-up load, which are done with the combination of the fantastic vant component PullRefresh and List.
First look at the effect map:
Static Screenshot:
2. Specific implementation steps
2.1. Page structure design, test data preparation.
Prepare a json file data locally and place it in the project public folder.Note that the local test data must be placed in the public folder for network requests to request data, which is vue3.x.Add a new axios dependency package for network requests.Partial screenshots, and key code:
//Data Request getDataList(){ this.$axios.get("/json/dataList.json").then((res)=>{ let list = res.data.data ? res.data.data: []; if (list.length > 0){ //from list Reclaim pageSize Bar data out var tempList = []; for (let i = 0; i < this.pageSize; i++){ if (list.length > 0){ let tempIndex = parseInt(Math.random() * 1000) % list.length; tempList.push(list[tempIndex]); list.splice(tempIndex, 1); } } this.loadImagesHeight(tempList); //Simulate pre-loaded pictures to get their height } else { this.loadImagesHeight(list); } }).catch((res)=>{ console.log("..fail: ", res); this.$toast.clear(); this.isLoading = false; //Drop-down refresh request complete this.loading = false; //Drop-up Load More Requests Completed }) },
2.2. Preload pictures, store picture height
After obtaining the data, traverse the data array, pre-load the pictures, calculate the height of the scaled pictures, and store them.Also since picture loading is asynchronous, count the variables and start rendering the page when the last picture loading is complete.
loadImagesHeight(list){ var count = 0; //Used to count whether all picture heights have been obtained list.forEach((item, index)=>{ //Create picture objects, load pictures, calculate picture height var img = new Image(); img.src = item.cover; img.onload = img.onerror = (e)=>{ count++; if (e.type == 'load'){ //Pictures loaded successfully //Calculate the height of the zoomed picture: the original height of the picture/Original width = Scaled Height/Scaled Width list[index].imgHeight = Math.round(img.height * this.boxWidth / img.width); // console.log('index: ', index, ', load suc, imgHeiht: ', list[index].imgHeight); } else{ //Picture failed to load, giving a default height of 50 list[index].imgHeight = 50; console.log("index: ", index, ", Load error:", e); } //Loading finishes the last picture height and begins the next step of data processing if (count == list.length){ this.resolveDataList(list); } } }) },
2.3, Render page, set absolute positioning
After all pictures get their height by preloading, start rendering the page.Then iterate through the box labels where all the pictures are located, get the height of the box, and set the absolute positioning of each box.
resolveDataList(list){ //Processing data //Drop-down refresh to empty the original data if (this.pageIndex <= 1){ this.itemCount = 0; this.dataList = []; this.lastRowHeights = [0, 0]; //Stores the last row height of each column in clear 0 } if (list.length >= this.pageSize){ this.pageIndex++; //Next page } else{ this.finished = true; //current tab All data of type has been loaded } //Merge old and new array data this.dataList = [...this.dataList, ...list]; //Determine if a page has data this.haveData = this.dataList.length > 0 ? 2 : 1; this.isLoading = false; //Drop-down refresh request complete this.loading = false; //Drop-up Load More Requests Completed console.log("...datalist: ", this.dataList); console.log("...this.isLoading: ", this.isLoading) this.$nextTick(()=>{ setTimeout(()=>{ //Rendering complete, calculating each item Width and height, set label coordinate positioning this.setItemElementPosition(); this.isLoading = false; //Drop-down refresh request complete this.loading = false; //Drop-up Load More Requests Completed }, 1000) }); }, //Get each item Label height, settings item Location of setItemElementPosition(){ let parentEle = document.getElementById('data-list-box'); let boxEles = parentEle.getElementsByClassName("data-item"); for (let i = this.itemCount; i < boxEles.length; i++){ let tempEle = boxEles[i]; //Column index of minimum height of previous label let curColIndex = this.getMinHeightIndex(this.lastRowHeights); let boxTop = this.lastRowHeights[curColIndex] + this.boxMargin; let boxLeft = curColIndex * (this.boxWidth + this.boxMargin) + this.boxMargin; tempEle.style.left = boxLeft + 'px'; tempEle.style.top = boxTop + 'px'; this.lastRowHeights[curColIndex] = boxTop + tempEle.offsetHeight; // console.log('i = ', i, ', boxTop: ', boxTop, ', eleHeight: ', tempEle.offsetHeight); } this.itemCount = boxEles.length; //Modify the height of parent labels let maxHeight = Math.max.apply(null, this.lastRowHeights); parentEle.style.height = maxHeight + 'px'; this.$toast.clear(); console.log("...boxEles: ", boxEles.length, ", maxH: ", maxHeight); },
2.4. Other Instructions
Other pages include functions such as pull-refresh and pull-load, which are used by Supported Component Library In PullRefresh and List This set of composite components.It feels great and the steps are easy to use.Another is that when the page is rendered, there will be page flicker phenomenon, which is handled by a css animation, and the effect is much better.But there was still a slight flicker on the first load.Wait until you find a better way to update.
Full effect DEMO address: https://github.com/xiaotanit/tan_vue/blob/master/src/views/PageWaterFall.vue