⚠️ Эта статья является первой подписанной статьей сообщества Nuggets, и ее перепечатка без разрешения запрещена.
предисловие
百万PV商城系列
В основном включает мой в проект торгового центра实践心得
, я начну с视觉体验
,性能优化
,架构设计
Начиная с трех измерений, я шаг за шагом объясню внешний вид проекта торгового центра.出现的问题
,问题解决的思路与方案
, и наконец代码实践
. Это сделает вас более удобным, когда вы столкнетесь с подобными проблемами на работе.
Эта статья является первой в серии практических рекомендаций по работе с торговыми центрами. Основное ее содержание — оптимизация загрузки ресурсов изображений в распространенных сценариях в проектах торговых центров для улучшения визуального восприятия и производительности страниц.
задний план
в повседневной жизни,Yoyo们
Должно быть, использовали некоторые компании электронной коммерцииApp
,Web
,小程序
и другие приложения. В процессе просмотра будет много изображений, таких как общие карточки продуктов, сведения о продукте, карусельные изображения, рекламные изображения и другие компоненты, которые необходимо использовать для некоторых загруженных ресурсов изображения конфигурации управления фоном.
Кроме того, большинство страниц, на которых расположены эти компоненты, — это страницы с очень большой нагрузкой по трафику, и если пользовательский опыт и смысл относительно невелики, это неизбежно скажется на удержании и конверсии пользователя. Итак, в этой статье мы изучим некоторые навыки решения задач по оптимизации ресурсов изображения.Я по очереди представлю фактические операции в работе из трех сценариев: обычных изображений, изображений высокой четкости и рендеринга на большом экране. Если вы столкнулись с соответствующим сценарием оптимизации, вы можете не волноваться.
Обычная оптимизация изображения
Для общей оптимизации изображения я использую ленивую загрузку в качестве основного метода реализации. Проще говоря, ленивая загрузка означает, что ресурсы изображения в визуальном окне загружаются первыми, а содержимое за пределами визуального окна будет загружаться только после прокрутки в визуальное окно.
Итак, почему я использую ленивую загрузку и какова реализация ленивой загрузки? Ниже я передаю простойReact
Реализация схемы ленивой загрузки изображения, чтобы объяснить шаг за шагом.
Зачем использовать ленивую загрузку?
Вообще говоря, большое количество ресурсов изображений будет загружено, как только вы войдете на веб-страницу, что косвенно повлияет на загрузку страницы, увеличит время загрузки белого экрана и повлияет на работу пользователя. Поэтому наше обращение不在可视化窗口
Изображения внутри не загружаются счастливо, насколько это возможно减少本地带宽
отходы и请求资源
количество.
Итак, почему я рекомендую ленивую загрузку в качестве основного метода реализации общих ресурсов изображений? Потому что у него есть два преимущества.
- Уменьшите потребление ресурсов полосы пропускания и уменьшите ненужное потребление ресурсов при загрузке.
- Предотвратите блокировку загрузки ресурсов, вызванную одновременной загрузкой ресурсов изображения, тем самым сократив время белого экрана.
Реализовать простую ленивую загрузку
Итак, как реализовать ленивую загрузку? Есть два способа добиться этого.
- пройти через
scroll
События для прослушивания реализации области прокрутки окна. Этот метод имеет хорошую совместимость, и большинство браузеров иWebView
совместимы и поддерживаются. - пройти через
IntersectionObserver API
наблюдатьDOM
Независимо от того, появляется ли он в окне или нет, преимущество этого метода в том, что его просто вызвать, но совместимость с некоторыми мобильными терминалами не так хороша, как у предыдущего метода.
Обе формы наблюдают за текущимDOM
Появляется ли оно в видимом окне, если да, то будетdata-src
Адрес изображения в назначенsrc
, затем начинает загрузку текущего изображения.
Итак, приступим к реализацииscroll
Пример ленивой загрузки событий.
Макет страницы
Сначала мы рисуем базовый макет страницы, в основном для загрузки окон и изображений.
const ImageLazy = () => {
const [list, setList] = useState([
1,2,3,4,5,6,7,8
])
const ref = useRef<HTMLDivElement | null>(null)
return (
<div className="scroll-view" ref={ ref }>
{list.map((id) => {
return (
<div key={id} className="scroll-item">
<img
style={{ width: '100%', height: '100%' }}
data-src={ `${ prefix }split-${id}.jpg` }
/>
</div>
);
})}
</div>
)
}
.scroll-item {
height: 200px;
}
.scroll-view {
height: 400px;
overflow: auto;
}
Вы можете видеть рендеры, на странице отображаются только две картинки, но на самом деле все картинки загружены.
Зарегистрируйтесь на события прокрутки
заscroll-view
границаref
После этого также необходимоuseEffect
средняя параscroll
События связаны и незарегистрированы.
Таким образом, я сначала получаю все текущие компонентыimg
Элемент (фактическая операция лучше всего использовать указанное className), которыйref.current
провестиaddEventListener
Добавьте операцию прослушивателя событий, а затем выполните соответствующий метод в обратном вызове.
В то же время вreturn
Когда событие происходит, также необходимо удалить его событие, чтобы избежать некоторых непредвиденных ситуаций.
useEffect(() => {
const imgs = document.getElementsByTagName('img');
console.log(ref.current, 'current')
ref.current?.addEventListener('scroll', () => {
console.log('listens run')
})
return (
ref.current?.removeEventListener('scroll', () => {
console.log('listens end')
})
)
}, [])
Как показано ниже, когда я прокручиваю, он выполняется одновременноScrollCallback
, консоль выводит много результатов выполнения, а это значит, что наше событие успешно добавлено.
Обратный вызов прокрутки и функция дроссельной заслонки
Из приведенной ниже диаграммы анализа мы видим, чтоclientHeight
высота нашего окна, а вScrollView
который срабатывает каждый раз, когда вы прокручиваетеscroll
Обратный вызов метода, вы можете получить расстояние прокрутки окна текущей страницыscrollTop
.
Итак, как мы можем узнать, присутствует ли элемент на странице?
по элементуoffsetTop
свойство, вы можете узнать расстояние смещения текущего элемента сверху. Итак, когда мы получаем высоту окнаclientHeight
, расстояние прокаткиscrollTop
, и расстояние элемента от вершиныoffsetTop
, можно вывести следующий набор условных формул,视窗高度(dom.clientHeight) + 滚动距离(dom.scrollTop) > 元素距离顶部距离(image.offsetTop)
Чтобы определить, появляется ли текущий элемент в видимом диапазоне страницы.
Преобразование его в функциональный метод дает следующие результаты:
function scrollViewEvent (images: HTMLCollectionOf<HTMLImageElement>) {
// 可视化区域高度
const clientHeight = ref.current?.clientHeight || 0
// 滚动的距离
const scrollTop = ref.current?.scrollTop || 0
// 遍历imgs元素
for (let image of images) {
if (!image.dataset.src) continue
// 判断src是否已经加载
if (image.src) continue
//图片距离顶部距离
let top = image.offsetTop
// 公式
if (clientHeight + scrollTop > top) {
// 设置图片源地址,完成目标图片加载
image.src = image.dataset.src || ''
image.removeAttribute('data-src')
}
}
}
Вот я тоже прохожуahook
серединаuseThrottleFn
Выполните небольшую оптимизацию регулирования, чтобы избежать частых обратных вызовов функций. существует500ms
Внутри событие будет выполнено только один раз, чтобы избежать потребления производительности, вызванного дополнительным выполнением.
import { useThrottleFn } from 'ahooks'
// 截流函数hook
const { run } = useThrottleFn(scrollViewEvent, {
wait: 500
})
При этом мы вставляем егоscroll
Выполняется в обратном вызове события. Однако в начале компонента он не может быть активированscroll
событие, поэтому нам нужно вручную инициализировать данные изображения на текущей первой странице.
useEffect(() => {
const imgs = document.getElementsByTagName('img');
console.log(ref.current, 'current')
ref.current?.addEventListener('scroll', () => {
run(imgs)
})
run(imgs)
return () => {
ref.current?.removeEventListener('scroll', () => {
console.log('listens end')
})
}
}, [])
Пока что один из наших图片懒加载
Реализация в основном завершена, давайте посмотрим на эффект.
Для ленивой загрузки каждый
item
Лучше всего установить высоту, чтобы ленивая загрузка элемента страницы не отображалась в области просмотра из-за отсутствия высоты компонента, когда в начале нет изображения.
Высокоточная оптимизация изображения
Для высококачественных изображений многие создаются相关运营人员
Настроенные диаграммы активности, как правило их будет много в период акции微页面
,или图片链接
, на всем протяжении图片 + 热区
публикуется для просмотра пользователями.
Поэтому подавляющее большинство эксплуатационных требований заключается в максимально четком отображении соответствующих изображений. Тогда ленивая загрузка, очевидно, не является хорошим решением проблемы, поэтому я добавил несколько состояний загрузки, основанных на исходной ленивой загрузке, чтобы дать пользователям визуальный опыт.В настоящее время продукты на рынке в основном используют骨架屏
или渐进式加载
И другие решения, чтобы сделать переход отображения изображения более плавным, чтобы избежать смущения из-за сбоя загрузки или зависания загрузки изображений.
Как добиться
На следующем рисунке давайте сначала разберемся с логикой реализации. В компоненте изображения поочередно заменяются два изображения.Когда загружается изображение ресурса высокой четкости, его необходимо骨架图
или缩略图
Скрыть, показать загруженное изображение высокой четкости.
компонент изображения
После завершения анализа вы можете следовать за мной, чтобы реализовать простой компонент изображения черезimg
серединаonLoad
событие, чтобы определить, было ли загружено отображаемое изображение. через соответствующий状态(status)
для управления отображением и скрытием эскизов. Далее я буду использовать прогрессивную загрузку в качестве примера.Ссылаясь на рисунок ниже, мы реализуем простой компонент переключения состояний.
import React from "react";
import "./index.css";
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
thumb: string;
}
type ImageStatus = 'pending' | 'success' | 'error'
const Image: React.FC<ImageProps> = (props) => {
const [status, setImageStatus] = React.useState<ImageStatus>('pending');
/**
* 修改图片状态
* @param status 修改状态
*/
const onChangeImageStatus = (status: ImageStatus) => {
/** TODO setTime模拟请求时间 */
setTimeout(() => setImageStatus(status), 2000)
}
return (
<>
<img
className="image image__thumb"
alt={props.alt}
src={props.thumb}
style={{ visibility: status === 'success' ? "hidden" : "visible" }}
/>
<img
onLoad={() => onChangeImageStatus('success')}
onError={() =>onChangeImageStatus('error')}
className="image image__source"
alt={props.alt}
src={props.src}
/>
</>
);
};
export default Image;
пройти черезfilter: blur(25px);
Собственность, часть миниатюры, чтобы добавить размытие, поэтому вы можете избежать смущения некоторых из карт мозаики, чтобы достичь части земного стекла или гауссовского размытия некоторых небольших эффектов.
пройти через
onLoad
После события загрузки мы выполнили простую прогрессивную загрузку изображения, поэтому другие состояния загрузки, подобные скелетному экрану, также могут быть реализованы с помощью того же суждения о состоянии. Просто поменяйте миниатюры на другие компоненты и все.
Оптимизирован для длинных рендеров
На странице сведений о продукте операция настроит подробное описание изображений и текстов некоторых продуктов, не только качество изображений будет относительно высоким, но и изображения будут очень длинными. Так что, очевидно, мы не можем сказать, что можем напрямую получить картинку и отобразить ее на странице, если скорость интернета у пользователя относительно низкая, то прямо на странице появится длинная белая полоса или карта ошибок, которая не загружается. . . . Это явно не те результаты, которые нам нужны.
Что же тогда делать?
Давайте сначала посмотрим на детали продукта на платформах электронной коммерции, таких как Taobao.Когда вы нажмете на большую картинку, вы обнаружите, что отображается только часть изображения, и оно будет разделено на множество изображений одинакового размера для нас.
В соответствии с этой идеей мы также выполнили соответствующую оптимизацию сегментации изображения, разделив длинное изображение на несколько блоков одинакового размера, чтобы выполнить оптимизацию пакетного рендеринга, чтобы уменьшить нагрузку при одиночном рендеринге длинных изображений.
разрезать на кусочки
Затем я вырезаю изображение из длинного изображения смоделированного бэкэнда и по очереди отображаю вырезанные изображения на странице для отображения. Без лишних слов, давайте сразу к делу.
Прежде всего, в сочетании со следующей диаграммой анализа, наш принцип разрезания диаграммы на самом деле очень прост.Разделите длинное изображение на маленькие изображения с одинаковой длиной и шириной.Если последнее изображение не удовлетворяет切割块高度
Если это так, непосредственно обрежьте оставшуюся высоту на изображениях.
Итак, позвольте мне написать простой код узла, чтобы показать вам процесс разрезания графа.
Срез узла
Как показано на рисунке ниже, я смоделирую длинную картинку, загруженную операцией, а затем разрежу ее на несколько частей нужной высоты.200
Короткое изображение (размер по оценке потребностей). Теперь давайте взглянем на учебник и код для достижения эффекта.
Сначала установитеsharp
для обработки изображений,image-size
Используется для получения информации о размере изображения.
# shell
yarn add sharp image-size
После импорта соответствующего пакета перейдитеimage-size
Получите информацию об исходном длинном изображении, которое нам нужно вырезать, напримерwidth
иheight
.
const sharp = require('sharp')
const sizeOf = require('image-size');
const currentImageInfo = sizeOf('./input.jpg');
При получении высоты и ширины изображения это означает, что я могу передатьwhile
Цикл вычисляет высоту отрезанных аликвот.
/** @name 每块大小 200px height */
const SPLIT_HEIGHT = 200
/** @name 长图高度 */
let clientHeight = currentImageInfo.height
/** @name 切割小图高度 */
const heights = []
while (clientHeight > 0) {
/** @if 切图高度充足时 */
if (clientHeight >= SPLIT_HEIGHT) {
heights.push(SPLIT_HEIGHT)
clientHeight -= SPLIT_HEIGHT
} else {
/** @else 切割高度不够时,直接切成一张,高度清0 */
heights.push(clientHeight)
clientHeight = 0
}
}
Затем, когда я знаю размер вырезанного изображения, я могуheights
пройти через裁剪偏移
Нарезайте настоящие изображения, создавайте новые файлы и сохраняйте их.
В приведенном ниже коде я создаюmarginTop
смещение, каждый раз, когда он разрезается, он будетheight
Накапливайте смещение вниз до тех пор, пока вырезанное изображение не закончится в конце последней страницы. В настоящее времяmariginTop
это высота изображения.
/** @name 偏移量 */
let marginTop = 0
heights.forEach((h, index) => {
sharp('./input.jpg')
.extract({ left: 0, top: marginTop, width: currentImageInfo.width, height: h })
.toFile(`./img/split_${index + 1}_block.jpg`).then(info => {
console.log(`split_${index + 1}_block.jpg切割成功`)
}).catch(err => {
console.log(JSON.stringify(err), 'error')
})
marginTop += h
})
Как показано ниже,img文件夹
немного больше零碎的图片
, затем проверьте, является ли изображение拼接完整
, если нет проблем, то мы выполнили требование простого длинного дайсинга карты, и следующим шагом будет поставить его на передний конец渲染
.
Фронтальный дисплей
После того, как фронтенд получает соответствующие срезы, они напрямую собираются и отображаются на странице фронтенда, после удаления пробелов или заусенцев в середине ничем не отличается от рендеринга длинных изображений. При рендеринге я все еще делаю простую оптимизацию загрузки в виде ленивой загрузки, общий эффект такой:
Прочитав эффект загрузки, давайте посмотрим на время отклика загрузки.
Так как я вырезал картинку и не сделал文件上的优化
, поэтому единственный граф существует体积过大
, но это никак не влияет на загрузку первого экрана Вы можете увидеть картинку ниже.Flow3G
Сравнение времени загрузки ниже.
Для одного длинного изображения пользователь может увидеть около 4 секунд.白屏
или骨架屏
, а загрузка после нарезки может предпочтительно отображать часть содержимого для пользователя.
Устранение заусенцев или зазоров
В окончательной реализации этого решения могут быть некоторые дефекты, а конкретный план раскроя связан с моделью оборудования. Если у вас возникнут проблемы, вы можете обратиться к некоторым из моих решений:
- Первый через
vertical-center
Установите значение вертикального центра, чтобы решить проблемы с выравниванием базовой линии. - Второй -
img
установить на реальныйblock
Элементы решены. - Третий, который я обычно использую, через
flex-direction
Установить какcolumn
Вертикальное расположение дочерних элементов решает проблему. - Четвертый через
background
решение задач с картинками. Но при этом, если вы хотите использовать ленивую загрузку, вам нужно изменить частьcss
Смещение стиля для достижения видимого отображения окна.
Справочные ресурсы
- # Progressively Loading Images In React
- # Оптимизация производительности интерфейса - картинки
- # Ленивая загрузка и предварительная загрузка
- # Отложенная загрузка компонентов для оптимизации производительности: введение в Vue Lazy Component
- # Образец изображения и демонстрационный адрес
Суммировать
В этой статье я рассказал о стратегиях оптимизации для трех разных изображений. Их уже много на рынке开源库
более удобный в реализации懒加载
и渐进加载
Путь. В то же время для каркасного экрана во многих библиотеках компонентов есть соответствующие компоненты, и стоимость упаковки также невелика.
Поэтому, если в проекте действительно задействовано много ресурсов изображений, то схема оптимизации, упомянутая в статье, — это то, что я рекомендую.
- Если его можно сделать ленивым, постарайтесь не загружать его полностью
- Дайте пользователю определенную подсказку о статусе и постарайтесь не открывать каркасный экран или диаграмму перехода.
- Длинный граф можно максимально обрезать, и его очень удобно разбирать для оптимизации.
- Изображения, которые можно сжать, должны быть максимально сжаты.
Для проекта торгового центра задача заключается не в логике реализации функций, а в оптимизации части визуального опыта и впечатлений. Если вы считаете, что статья полезна для вас, вы можете нажать 👍 и подбодрить меня. Если вы хотите узнать больше о внешних проектах электронной коммерции, Yoyos может подписаться на эту колонку.
последние хорошие статьи
- # [Избранное будет] Browser WebStorage Cache Usage Guide
- #Я и Наггетс, через год после выпуска я подписал контракт с Наггетс|2021 в середине резюме
- # Обобщить практический опыт применения TypeScript в разработке проектов
концевые сноски
Эта статья была впервые опубликована в: Nuggets Technology Community
Тип: Подписанная статья
Добавить Автора
Любимое в колонке:# Миллионы серии PV Mall Practice
Публичный номер: Жизнь программы ItCodes