У изображений в макете водопада есть основная особенность - одинаковая ширина и переменная высота Макет водопада используется в определенном масштабе на отечественных и зарубежных веб-сайтах, таких какpinterest,Лепесткии т.п. Затем, основываясь на этой особенности, начните тур по исследованию потока водопада.
Чтобы обеспечить актуальность и точность контента, вы можете обратить внимание наличный блог
Реализация основной функции
Сначала мы определяем контейнер с 20 изображениями,
<body>
<style>
#waterfall {
position: relative;
}
.waterfall-box {
float: left;
width: 200px;
}
</style>
</body>
<div id="waterfall">
<img src="images/1.png" class="waterfall-box">
<img src="images/2.png" class="waterfall-box">
<img src="images/3.png" class="waterfall-box">
<img src="images/4.png" class="waterfall-box">
<img src="images/5.png" class="waterfall-box">
<img src="images/6.png" class="waterfall-box">
...
</div>
Из-за неизвестных очков знаний CSS девушка с самыми длинными чулками занимает все место внизу. . .
Рядом с текстом, если в каждой строке 5 столбцов, как показано на картинке выше, то 6-я картинка должна появиться под какой из первых 5 картинок? Разумеется, он расположен абсолютно под картинкой с наименьшей высотой из первых 5 картинок.
А что на 7 картинке? На этот раз, после того, как мы сделали шестой снимок и снимки над ним в целом, идея та же, что и выше. Код реализован следующим образом:
Waterfall.prototype.init = function () {
...
const perNum = this.getPerNum() // 获取每排图片数
const perList = [] // 存储第一列的各图片的高度
for (let i = 0; i < perNum; i++) {
perList.push(imgList[i].offsetHeight)
}
let pointer = this.getMinPointer(perList) // 求出当前最小高度的数组下标
for (let i = perNum; i < imgList.length; i++) {
imgList[i].style.position = 'absolute' // 核心语句
imgList[i].style.left = `${imgList[pointer].offsetLeft}px`
imgList[i].style.top = `${perList[pointer]}px`
perList[pointer] = perList[pointer] + imgList[i].offsetHeight // 数组最小的值加上相应图片的高度
pointer = this.getMinPointer(perList)
}
}
Внимательные друзья могут обнаружить, что используется высота картинки, полученная в кодеoffsetHeightЭтот атрибут, сумма высот этого атрибута равна图片高度 + 内边距 + 边框, из-за этого мы используем padding вместо margin для установки расстояния между изображениями. В дополнение кoffsetHeightсвойства, помимо пониманияoffsetHeight,clientHeight,offsetTop,scrollTopРазницу между свойствами и другими атрибутами можно лучше понять, чтобы понять проект. Код css такой же простой, как и следующий:
.waterfall-box {
float: left;
width: 200px;
padding-left: 10px;
padding-bottom: 10px;
}
На данный момент базовая компоновка потока водопада завершена, и рендеринг выглядит следующим образом:
Реализация прослушивателей событий прокрутки и изменения размера
После реализации функции инициализации init следующим шагом является отслеживание события прокрутки, чтобы добиться эффекта непрерывной загрузки изображений при прокрутке до нижней части родительского узла. В это время нам нужно рассмотреть момент, когда положение прокрутки должно запускать функцию загрузки? Это варьируется от человека к человеку, мой подход заключается в том, чтобы удовлетворить父容器高度 + 滚动距离 > 最后一张图片的 offsetTopЭто условие, то есть оранжевая линия + фиолетовая линия > синяя линия, запускает функцию загрузки, код выглядит следующим образом:
window.onscroll = function() {
// ...
if (scrollPX + bsHeight > imgList[imgList.length - 1].offsetTop) {// 浏览器高度 + 滚动距离 > 最后一张图片的 offsetTop
const fragment = document.createDocumentFragment()
for(let i = 0; i < 20; i++) {
const img = document.createElement('img')
img.setAttribute('src', `images/${i+1}.png`)
img.setAttribute('class', 'waterfall-box')
fragment.appendChild(img)
}
$waterfall.appendChild(fragment)
}
}
Поскольку родительский узел может настраивать узел, он обеспечивает инкапсуляцию функции прокрутки монитора.Код выглядит следующим образом:
proto.bind = function () {
const bindScrollElem = document.getElementById(this.opts.scrollElem)
util.addEventListener(bindScrollElem || window, 'scroll', scroll.bind(this))
}
const util = {
addEventListener: function (elem, evName, func) {
elem.addEventListener(evName, func, false)
},
}
Мониторинг события изменения размера аналогичен мониторингу события прокрутки.Когда функция изменения размера запускается, вызовите функцию инициализации, чтобы сбросить ее.
Реализовать привязку прослушивателя с использованием шаблона публикации-подписки и наследования.
Поскольку цель разработки подключаемых модулей состоит в том, чтобы удовлетвориться реализацией функций, соответствующее рабочее пространство должно быть зарезервировано для разработчиков, чтобы они могли справиться с ними самостоятельно. Думается, что картинки, которые вытягиваются и загружаются в водопадном потоке в бизнес-сценарии, вообще получаются асинхронно от Ajax, поэтому загружаемые данные не должны записываться насмерть в библиотеку, а могут быть реализованы следующие вызовы (здесь для справки).waterfallПрименение),
const waterfall = new Waterfall({options})
waterfall.on("load", function () {
// 此处进行 ajax 同步/异步添加图片
})
Наблюдая за вызывающим методом, нетрудно подумать об использовании модели публикации/подписки для его реализации.Что касается модели публикации/подписки, мы обсудилиNode.js Асинхронный анекдотК нему есть введение. Основная идея состоит в том, чтобы добавить функцию в кеш через функцию подписки, а затем реализовать асинхронный вызов через функцию публикации.Реализация кода приведена ниже:
function eventEmitter() {
this.sub = {}
}
eventEmitter.prototype.on = function (eventName, func) { // 订阅函数
if (!this.sub[eventName]) {
this.sub[eventName] = []
}
this.sub[eventName].push(func) // 添加事件监听器
}
eventEmitter.prototype.emit = function (eventName) { // 发布函数
const argsList = Array.prototype.slice.call(arguments, 1)
for (let i = 0, length = this.sub[eventName].length; i < length; i++) {
this.sub[eventName][i].apply(this, argsList) // 调用事件监听器
}
}
Затем, чтобы разрешить Waterfall использовать модель публикации/подписки, просто позвольте Waterfall унаследовать функцию eventEmitter, и код будет реализован следующим образом:
function Waterfall(options = {}) {
eventEmitter.call(this)
this.init(options) // 这个 this 是 new 的时候,绑上去的
}
Waterfall.prototype = Object.create(eventEmitter.prototype)
Waterfall.prototype.constructor = Waterfall
Способ написания наследования поглощает преимущества наследования на основе конструктора и наследования на основе цепочки прототипов и используетObject.createИзолируйте подкласс и суперкласс.Для более подробной информации о наследовании вы можете написать другую статью, поэтому я остановлюсь здесь.
Небольшая оптимизация
Чтобы событие прокрутки не запускало многократную загрузку изображений, вы можете рассмотреть возможность реализации функции анти-дрожания и дросселирования. На основе модели публикации-подписки параметр isLoading определяется, чтобы указать, загружается ли он, и определяется, следует ли загружать, в соответствии с его логическим значением.Код выглядит следующим образом:
let isLoading = false
const scroll = function () {
if (isLoading) return false // 避免一次触发事件多次
if (scrollPX + bsHeight > imgList[imgList.length - 1].offsetTop) { // 浏览器高度 + 滚动距离 > 最后一张图片的 offsetTop
isLoading = true
this.emit('load')
}
}
proto.done = function () {
this.on('done', function () {
isLoading = false
...
})
this.emit('done')
}
В это время нужно добавить место, откуда осуществляется звонокwaterfall.done, чтобы сообщить, что текущее изображение было загружено, код выглядит следующим образом:
const waterfall = new Waterfall({})
waterfall.on("load", function () {
// 异步/同步加载图片
waterfall.done()
})
адрес проекта
Использование этого плагина в проектах React
Проект простой, недочеты неизбежны, оставляйте свои ценные комментарии.