Небольшой программный водопад картинок (наиболее полная реализация, дополнительная ленивая загрузка)

Node.js внешний интерфейс GitHub jQuery
Небольшой программный водопад картинок (наиболее полная реализация, дополнительная ленивая загрузка)

визуализация

Иди, иди, смотри, смотри, как прекрасен внешний мир,

Рендеринг показывает эффект макета водопада и ленивой загрузки.

данные

Источник данных изображенияИнтернет-журнал Чжан Синьсюй

Давайте сначала поговорим о нашем формате ссылки на изображение.

Все ссылкиhttp://cued.xunlei.com/demos/publ/img/P_${name}.jpgВ этом формате нам нужно изменить значение имени, когдаnameКогда значение меньше 10, формат00x,как002,003, когда оно больше 10,023Этот вид.

определение

Макет водопадного потока — популярный метод макета страницы. Самый ранний веб-сайт, принявший этот макет, — Pinterest.Ширина изображения фиксированная, а высота задается автоматически, что создает зубчатую эстетику.

принцип

Принцип очень прост, в основном делится на следующие шаги

1. Определите массив высоты и количество столбцов

2. Траверсные элементы, количество которых меньше количества колонн непосредственноpushв массив

3. Если оно больше количества столбцов, получить наименьшее значение в массиве высот и определить верхнее и левое значения элемента

4.важная точкаОбновите массив высот, добавив минимальную высоту к высоте текущего элемента

Зная принцип, как надо писать код? Вот пример на веб-стороне, который примерно выглядит следующим образом

let heightArr = []
let col = 2
let allBox = document.querySelectorAll('.box') // 获取所有盒子

for(let i in allBox){
	
	let boxWidth = allBox[0].offsetWidth  // 获取盒子宽度 都一样直接取第一个
  let boxHeight = allBox[i].offsetHeight
	if(i < col){	
		heightArr.push(boxHeight)  // 把第一行高度都添加进去
	} else {  // 进行布局操作
		
		let minHeight = Mac.min.apply(null, heightArr)  // 获取最小高度
		let minIndex = getIndex(heightArr, minHeight)  // 获取最小高度的下标 要不就是0 要不就是1
		allBox[i].style.position = 'absolute'
		allBox[i].style.top = minHeight + 'px'
		allBox[i].style.width = minIndex * boxWidth + 'px'
		
		heightArr[minIndex] += boxHeight // 更新最新高度 
	}
}

// 获取下标
getIndex(arr, val){
	for(i in arr){
		if(arr[i] == val) {
			return i
		}
	}
}

Выше приведена основная логика реализации водопадного потока, которая примерно написана здесь Далее давайте посмотрим, как реализован апплет.

выполнить

На веб-странице мы можем напрямую получать и управлять DOM, что очень удобно реализовать, не говоря уже о том, что существует множество подключаемых модулей jquery, которые можно использовать. Мы знаем, что в апплете нет DOM, так как же его реализовать? Нам просто нужно изменить свое мышление.

Здесь мы реализуем макет потока водопада тремя способами.

CSS

Проще всего использовать css3 для достижения, давайте сначала выберем простой,

использоватьcolumn-countномер столбца набора свойств

использоватьwx-ifПримите решение отображать изображение влево или вправо

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> 

wxss

.container{
  column-count: 2;  /*设置列数*/ 
  column-gap:2rpx;
  padding-left: 8rpx;
}
image{
  width: 182px;
  box-shadow: 2px 2px 4px rgba(0,0,0,.4);
}

js может получить данные, поэтому я не буду здесь вдаваться в подробности.

Информация об узле

Апплет может проходитьAPI информации об узле WXMLЧтобы получить информацию об элементе, давайте код.

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> 

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%;
}

js

Ссылка на изображениеhttp://cued.xunlei.com/demos/publ/img/P_${name}.jpg, просто нужно изменить имя

Сначала обработайте наши данные

// 创建长度为30的数组
const mockData = () => {
  return Array.from(Array(30).keys()).map(item => {
    if (item < 10) {
      return '00' + item
    } else {
      return '0' + item
    }
  })

}
// 扩展成我们需要的数据
const createGroup = () => {
  let group = []
  let list = mockData()
  list.forEach(item => {
    group.push({ name: item, position: 'static', top: '', left: '' })
  })
  return group
}

Затем выполните макет потока водопада, основной код выглядит следующим образом.

load(e){  // 监听图片加载完 获取图片的高度
    this.setData({
      height: [...this.data.height, e.detail.height]
    })
    this.showImg()  // 调用渲染函数
},

showImg(){
    let height = this.data.height
    if (height.lenth != this.data.group .legth){  // 保证所有图片加载完
      return
    }
    setTimeout(()=>{ // 异步执行
      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)
    
  }

Видно, что логика реализации аналогична вышеописанной, но здесь мы модифицируем данные, ведь апплет управляется данными.

Здесь мы в основном следимimageкомпонентbindloadСобытие для получения высоты каждой картинки, высоту можно использовать для верстки, большую часть времени также используется для загрузки картинок, можно ли оптимизировать? Конечно, мы можем использовать узел для переноса данных.

Серверная обработка данных

Выше мы упоминали, что получение высоты картинки внутри апплета — неблагодарное дело, мы используем node для получения высоты картинки, а затем упаковываем ее для использования апплетом.

  • использоватьrequestсделать запрос
  • использоватьimage-sizeПолучить высоту изображения
  • Наконец, данные будут записаны в файл после получения, и будет запущена служба для предоставления интерфейса.

Вот основные проблемы, с которыми столкнулись

1. Запрос модуля request по умолчанию возвращает строку типа String.При использовании модуля image-size на входе должен быть Buffer.Как его разбить? Задайте в запросе запросencodingзаnullПросто

2. Мы просканировали здесь 100 изображений. Как мы можем убедиться, что мы закончили их сканирование? можно написать так

Promise.all(List.map(item => getImgData(item)))  // getImgData函数是获取图片的函数 会返回个promise

3. Если вы запрашиваете несколько раз и обнаруживаете, что некоторые картинки не могут быть получены, и сообщается об ошибке, что происходит?Ведь они сделали антисканирование, поздравляю с выигрышем, и попробуйте еще раз с другим IP (вы можно поставить код на сервер) , или поменять на вайфай) на самом деле нам нужно сканировать только один раз, а зачем сканировать после того как файл сгенерирован.

Пожалуйста, нажмите, чтобы увидеть полный кодgithub

Мы возвращаемся к апплету, и данные, возвращаемые интерфейсом, выглядят следующим образом.

Вы можете видеть, что у каждого изображения есть высота. Далее мы реализуем макет потока водопада. Подождите, мы сделаем ленивую загрузку макета потока водопада. Что касается ленивой загрузки апплета, нажмите сильновыучить больше.

Как этого добиться? В основном делится на два этапа

1. Расположите элементы в виде водопада

2. Создайте IntersectionObserver для ленивой загрузки

Начнем с нашего макета

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>

Выше мы используемwx-ifпройти черезshowЭто поле используется для определения того, загружено ли изображение.

использоватьviewКомпоненты используются для того, чтобы иметь место, а затем изменитьshowполе для отображения изображений

js

Мы используем два цикла for, первый для макета

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
    })

После завершения макета создайте IntersectionObserver для динамического определения отображения узла изображения.

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
        })
 })
}

Наконец

Мы использовали три метода для завершения компоновки каскадного потока апплета и дополнительно выполнили ленивую загрузку на основе каскадного потока. Можно обнаружить, что использование css является наиболее удобным, хотя апплет не может работать с DOM, но после изменения данных это фактически то же самое, что и изменение DOM, изменение концепции, и разработка апплета все еще очень крутая. .

Наконец, всем удачных выходных.

github