предисловие
Недавно мне нужно было сделать плагин для предварительного просмотра изображений. После просмотра изображений для предварительного просмотра многих продуктов (Nuggets, Zhihu, Jianshu, Graphite и т. д.) я наконец почувствовал, что графит больше соответствует потребностям нашего продукта.
Я думал, что смогу найти похожие плагины в сообществе, но идея была хороша, но реальность была очень скудной, поэтому я решил сделать один сам, и, кстати, я также изучил процесс разработки компонентов.
Введение в проект
Предварительный просмотр проекта
Окончательный эффект реализации проекта, как показано ниже, в основном такой же, как и предварительный просмотр изображения графита. служба поддержкиУвеличить изображение,Уменьшить,дисплей оригинального размера,Изображение подходит для экрана,Скачать изображения (все еще в разработке), которые представляют собой пять кнопок действий на нижней панели.
стек технологий
компоненты основаны наReact Hooks
а такжеTypeScript
Реализовано, инструмент упаковки используетwebpack
.
Эта статья о
webpack
Конфигурация не будет введена соответственно, если вы правыwebpack
Если вам интересно, вы можете обратиться к авторской подборкеГоворя об оптимизации производительности Webpack (с огромными подробными примечаниями по изучению Webpack).
каталог проекта
.
├── node_modules // 第三方的依赖
├── config // webpack 配置文件夹
├── webpack.base.js // webpack 公共配置文件
├── webpack.dev.config.js // 开发环境配置文件
└── webpack.prod.config.js // 生产环境配置文件
├── example // 开发时预览代码
├── src // 示例代码目录
├── app.js // 测试项目 入口 js 文件
└── index.less // 测试项目 入口 样式文件 文件
├── src // 组件源代码目录
├── components // 轮子的目录
├── photoGallery // photoGallery 组件文件夹
├── types // typescripe 的接口定义
├── utils // 工具函数目录
├── images // 图片文件目录
├── index.html // 项目入口模版文件
├── index.tsx // 项目入口文件
└── index.less // 项目入口样式文件
├── lib // 组件打包结果目录
├── .babelrc // babel 配置文件
├── .gitignore // git上传时忽略的文件
├── .npmignore // npm 上传忽略文件
├── README.md
├── tslint.json // tslint 的配置文件
├── tsconfig.json // ts 的配置文件
├── package-lock.json // yarn lock 文件
└── package.json // 当前整一个项目的依赖
Адрес склада
Адрес склада здесь:Плагин предварительного просмотра изображений имитации графита.
Анализ мыслей
Ядро этого плагина заключается в отображении изображений и операциях с изображениями предварительного просмотра, таких какувеличить,уменьшить,По размеру экрана, и эти операции связаны с размером картинки.На самом деле, пока мы знаем, какого размера должно отображаться изображение при нажатии на кнопку соответствующей операции, вся проблема решена.
Так что автор изучил волну превью логики за этим и нашел несколько полезных моментов для кодинга:
Во-первых, картинку нельзя все время приближать-отдалять.У нее должно быть максимальное значение и минимальное значение.После волны операций выяснилось, что в графитеМаксимальное значение изображения предварительного просмотра в 4 раза больше, чем исходное изображение.,Минимальное значение в 10 раз превышает исходное изображение., при этом необходимо указать количество кликов от исходного изображения до максимального или минимального значения.Количество раз, которое я указал в плагине6
Второсортный.
Таким образом, после загрузки изображения мы можем легко вычислить все размеры изображения для предварительного просмотра, и мы можем сохранить эти размеры в массиве, так что за каждым кликом увеличения и уменьшения будет изображение соответствующий ему размер.
Затем нам нужно знать, что размер отображения текущего изображения предварительного просмотра находится вмассив измеренийкакой изindex
, с этимindex
После этого нам просто нужно удалить этоindex
Соответствующая ширина изображения может быть нарисована.
Это первый раз, когда изображение отображается в контейнере.В качестве примера возьмем длинное изображение: при предварительном просмотре длинного изображения плагин оставит определенное расстояние по верхней и нижней сторонам изображения.Это расстояние на самом деле Зазоры, оставленные на верхней и нижней сторонах, равны высоте контейнера.5%
, вы можете увидеть картинку ниже (простите, картинка очень волшебная),на фотоA
РасстояниеB
из5%
.
Таким образом, мы можем вычислить размер текущего изображения и принять этот размер кмассив измеренийНайдите ближайшее значение к этому значению, индекс этого ближайшего значения — текущее изображение предварительного просмотра.index
стоимость.
Существует также предварительное изображение графита черезcanvas
Нарисуйте его, мы также будем использовать его здесьcanvas
изdrawImage
этоapi
Рисовать картинки, конечно, не поддерживаетcanvas
В браузере мы напрямую используем<img />
Этикетка.
В данной работе основной анализ
canvas
нарисовать этот фрагмент контента,<img />
Этикетки действительно похожи.
На данный момент трудности этого плагина в основном решены, и тогда мы приступим к анализу соответствующего кода.
анализ кода
Плагин получает параметры
Во-первых, давайте взглянем на параметры, необходимые плагину, которые можно условно разделить на следующие:
-
visible
: управление отображением и скрытием плагина предварительного просмотра. -
imgData
: Массив изображений для предварительного просмотра -
currentImg
: при повторном открытии плагина предварительного просмотра первые несколько изображений отображаются по умолчанию. -
hideModal
: Как закрыть плагин предварительного просмотра
Четырех, о которых я могу думать, на данный момент достаточно, они используются следующим образом:
<PhotoGallery
visible={visible}
imgData={ImgData}
currentImg = {9}
hideModal={
() => {
setVisible(false);
}
}
/>
Структура плагина
Структура плагина на самом деле очень проста, по сути, состоит из трех частей:блок отображения изображений,Боковая панель выбора списка изображений,Нижний блок действий, определяемый как три блока подкомпонентов:<Canvas />
,<Sidebar />
,<Footer />
, унифицированный под управлением родительского компонента.
Потому что мы в основном объясняем
canvas
нарисовать картинку, поэтому блок отображения картинки установлен на<Canvas />
,Не поддерживаетсяcanvas
браузер, который будет использоваться в исходном коде<Image />
Компоненты используются для отображения изображений, которые здесь не будут подробно описаны, вы можете обратиться к исходному коду.
Код родительского компонента выглядит следующим образом:
// src/components/photoGallery/index.tsx
import React, { useState } from 'react';
import classNames from 'classnames';
import { Footer, Sidebar, Canvas } from './components';
const photoGallery = (props: Props): JSX.Element => {
const { imgData, currentImg, visible } = props;
// 当前显示第几张图片
const [currentImgIndex, setCurrentImgIndex] = useState(currentImg);
return (
<div
className={
classNames(
styles.modalWrapper,
{
[styles.showImgGallery]: visible, // 根据 visible 渲染插件
}
)
}
>
<div className={styles.contentWrapper}>
<Canvas
// 要加载的图片 url
imgUrl={imgUrl}
/>
</div>
<Sidebar
// 图片数组
imgData={imgData}
/>
<Footer
// 图片数量
imgsLens={imgData.length}
// 当前第几张
currentImgIndex={currentImgIndex}
/>
</div>
);
}
Как показано на рисунке выше, общая структура плагина завершена, и следующим шагом является логика основного модуля отображения изображений.
Основная логика предварительного просмотра изображения
Сначала создадим классcanvas.ts
, для предварительного просмотра изображения мы все работаем в этом классе.
Этот класс принимает два параметра, один из которых является контейнером для рендеринга.dom
, Является еще одним примером необходимости использования параметровoptions
, следующееoptions
Реализация интерфейса:
interface CanvasOptions {
imgUrl: string; // 图片地址
winWidth: number; // 屏幕宽度
winHeight: number; // 屏幕高度
canUseCanvas: boolean; // 浏览器是否可以使用 canUseCanvas
loadingComplete?(instance: any): void; // 制作图片 loading 效果
}
Кроме того, мы поговорим о ряде свойств, связанных с изображением предварительного просмотра, которые связаны со свойствами его экземпляра, например:
-
el
: визуализированный контейнер -
canUseCanvas
: поддерживать лиcanvas
, решить, как нарисовать график -
context
:canvas
холстgetContext('2d')
-
image
: Предварительный просмотр объекта изображения -
imgUrl
:предварительный просмотр изображенияurl
-
imgTop
: Цель в правом верхнем углу изображения.canvas
серединаy
высота оси -
imgLeft
: Цель в правом верхнем углу изображения.canvas
серединаx
высота оси -
LongImgTop
: расстояние изображения от верхней части контейнера, используемое для прокрутки и перетаскивания изображения. -
LongImgLeft
: расстояние между изображением и левой стороной контейнера, используемое для прокрутки и перетаскивания изображения. -
sidebarWidth
: ширина боковой панели -
footerHeight
: Высота нижней полосы -
cImgWidth
: ширина изображения на холсте. -
cImgHeight
: Высота картины в холсте -
winWidth
: ширина экрана -
winHeight
: высота экрана -
curPos
: Нужно перетаскивать картинку мышкойx/y
стоимость -
curScaleIndex
: текущее отображаемое изображение, которое находится в массиве измеренийindex
-
fixScreenSize
: использовать размер экрана из массива измеренийindex
стоимость -
EachSizeWidthArray
: Массив размеров изображения, включая значения ширины всех размеров, которые необходимо увеличить и уменьшить. -
isDoCallback
: загружено ли изображение
Значения атрибутов, используемые в плагине, в основном все вышеперечисленные.
Нарисуйте простую картинку
Сначала давайте посмотрим на этоcanvas
нарисуй этоapi
, который помогает нам нарисовать изображение, холст или видео на холсте.
Мы можем увеличить масштаб следующими способами, которые помогут нам нарисовать картинку:
var c = document.getElementById("myCanvas");
// 创建画布
var ctx = c.getContext("2d");
// 开始绘制
ctx.drawImage(image, dx, dy, dWidth, dHeight);
Параметры означают:
-
image
: указывает изображение, холст или видео для использования. -
dx
:image
верхний левый угол мишениcanvas
начальствоX
координаты оси -
dy
:image
верхний левый угол мишениcanvas
начальствоy
координаты оси -
dWidth
:image
в цельcanvas
Ширина для рисования. -
dHeight
:image
в цельcanvas
Высота для рисования.
В частности, вы можете увидеть следующий рисунок:
Для более подробного использования этого метода вы можете обратиться к:Документация MDN для drawImage.
с этимapi
После этого нам на самом деле просто нужно вычислить этоapi
Достаточно соответствующих 5 параметров.Для простого примера, как мы изменим следующую картинку, чтобы получить5
параметры:
image
объект
мы можем использоватьnew Image()
создать экземплярimage
объекта и указать егоsrc
атрибут - соответствующее изображениеurl
адрес, чтобы вы могли получитьimage
объект, когда изображение загружено, мы можем передатьimgDom.naturalWidth
а такжеimgDom.naturalHeight
Исходная ширина и высота изображения:
// src/components/photoGallery/canvas.ts
loadimg(imgurl) {
const imgDom = new Image();
imgDom.src = imgUrl;
imgDom.onload = function() {
// 图片加载完成之后
// 做你想要做的事情
}
}
-
dx
а такжеdy
,dwidth
а такжеdHeight
Атрибуты
Возьмем в качестве примера длинную картинку: мы анализировали ее, когда объясняли идею, и части, оставшиеся незаполненными в верхней и нижней частях,На изображении показана высота контейнераиз5%
, где мы определяем высоту нижнего блока (footerHeight
)для50px
, ширина боковой панели (sidebarWidth
)для120px
, что становится проблемой для поступления в начальную школу, мы можем передатьwindow.innerWidth
а такжеwindow.innerHeight
чтобы получить ширину экрана (winWidth
) и высокий (winHeight
), после расчета мы можем получить четыре нужных нам свойства:
/**
* winWidth:屏幕宽度
* winHeight:屏幕高度
* footerHeight:底部高度
* sidebarWidth:侧边栏宽度
* wrapperWidth:图片显示区域宽度
* wrapperHeight:图片显示区域高度
* naturalWidth: 图片原始宽度
* naturalHeight: 图片原始高度
*/
wrapperHeight = winHeight - footerHeight;
wrapperWidth = winWidth - sidebarWidth;
dy = wrapperHeight * 0.05;
dHeight = wrapperHeight - 2 * dy;
// 与原始宽高有个等比例的关系
dWidth = naturalWidth * dHeight / naturalHeight;
dx = (wrapperWidth - dWidth) / 2
Выше приведен процесс вычисления пяти нужных нам атрибутов, что в целом удобнее.
Поэтому каждый раз, когда мы хотим нарисовать картинку, нам просто нужно вычислить это.5
ценностьok
.
Исходная ширина и высота изображения
мы вutils
внизimg.ts
определить метод вgetBoundingClientRect
, получитьОтображаемая ширина и высота изображения и его расстояние от верхней части контейнераimgTop
, а расстояние слеваimgLeft
.
// src/utils/img.ts
/**
* 返回第一次加载图片的宽高,和 imgTop/imgLeft
* 通过返回的参数 直接 通过 drawImage 画图了
**/
export const getBoundingClientRect = (options: RectWidth): BoundingClientRect => {
const {
naturalWidth, // 图片原始宽
naturalHeight, // 图片原始高
wrapperWidth, // 显示容器宽
wrapperHeight, // 显示容器高
winWidth, // 屏幕宽度
} = options;
// 图片宽高比
const imageRadio = naturalWidth / naturalHeight;
// 显示容器宽高比
const wrapperRadio = wrapperWidth / wrapperHeight;
// 长图的逻辑
if (imageRadio <= 1) {
// 具体画布上方默认是 容器高度的 0.05
imgTop = wrapperHeight * 0.05;
// 图片的高度
ImgHeight = wrapperHeight - wrapperHeight * 0.05 * 2;
// 根据原始宽高,等比例得到图片宽度
ImgWidth = ImgHeight * naturalWidth / naturalHeight;
// 如果图片的宽高比显示容器的宽高比大
// 说明图片左右两侧的宽度需要固定为容器的宽度的 0.05 倍了
if (wrapperRadio <= imageRadio) {
ImgWidth = wrapperWidth - wrapperWidth * 0.05 * 2;
ImgHeight = ImgWidth * naturalHeight / naturalWidth;
imgTop = (wrapperHeight - ImgHeight) / 2
}
// ...
imgLeft = newWinWidth - ImgWidth / 2;
}
// 处理宽图的逻辑
// ...
// 返回
return {
imgLeft,
imgTop,
ImgWidth,
ImgHeight,
}
}
Для более подробного кода вы можете обратиться к исходному коду.
Массив размеров изображения предварительного просмотра
Как мы упоминали ранее, мы можем поместить все размеры изображения в процессе увеличения и уменьшения масштаба в массив, что удобно для получения соответствующего размера изображения через индекс, так как же с этим работать?
Фактически, пока изображение загружается, исходные ширина и высота изображения получаются, и соответствующий массив размеров вычисляется и вставляется в массив через исходную ширину и высоту и соответствующую формулу расчета.
определить классsetEachSizeArr
Метод экземпляра:
// src/components/photoGallery/canvas.ts
/**
* 计算图片放大、缩小各尺寸的大小数组,
*/
private setEachSizeArr () {
const image = this.image;
// 得到尺寸数组
const EachSizeWidthArray: number[] = getEachSizeWidthArray({
naturalWidth: image.width,
naturalHeight: image.height,
})
// 挂到实例属性上去
this.EachSizeWidthArray = EachSizeWidthArray;
// 得到适应屏幕的 index
// 也就是操作按钮中的 第四个按钮
const fixScreenSize = getFixScreenIndex({
naturalWidth: image.width,
naturalHeight: image.height,
wrapperWidth: this.cWidth,
wrapperHeight: this.cHeight,
}, EachSizeWidthArray);
// 将适应屏幕的 index 挂到实例属性
this.fixScreenSize = fixScreenSize;
}
getEachSizeWidthArray
Мы получаем массив размеров с помощью этого метода, потому что самое большое изображение в 4 раза больше исходного изображения, а самое маленькое изображение — это исходное изображение.1/10
, от самого маленького до исходного изображения и от исходного изображения до самого большого нужно пройти6
Во-вторых, мы можем получить размер каждого размера в соответствии с соотношением, и автор не будет публиковать конкретный код.
getFixScreenIndex
Мы используем этот метод, чтобы получить размер массива, адаптированный к экрану.index
, принцип заключается в том, что первая ширина и высота в массиве размеров меньше ширины и высоты отображаемого контейнера.index
.
Конкретный код этих двух методов не будет опубликован.Если вам интересно, вы можете проверить исходный код.
Начальный превью индекс изображения
Нам нужно выяснить, какой из них находится в массиве измерений при рендеринге первого изображения.index
, так как мы получаем ширину первого отрендеренного изображения, мы можем сравнить эту ширину с массивом в массиве размеров, и индексом ближайшего значенияindex
, которое является текущим изображениемindex
стоимость:
// src/components/photoGallery/canvas.ts
/**
* 设置当前 EachSizeWidthArray 的索引,用于 放大缩小
*/
private setCurScaleIndex() {
const cImgWidth = this.cImgWidth || this.image.width;
const EachSizeWidthArray = this.EachSizeWidthArray;
const curScaleIndex = getCurImgIndex(EachSizeWidthArray, cImgWidth);
this.curScaleIndex = curScaleIndex;
}
getCurImgIndex
Мы используем этот метод, чтобы получить значение индекса текущего изображения, основанное на ширине отображаемого в данный момент изображения, чтобымассив измеренийВыньте ширину, ближайшую к изображению предварительного просмотра, чтобы получить текущую ширину изображения.index
, вы можете обратиться к исходному коду конкретной реализации.
логика увеличения и уменьшения масштаба
Логика увеличения превью на самом деле состоит в том, чтобы рассчитать расстояние до текущего изображения в соответствии с увеличенным размером.canvas
высота вершиныimgTop
, а расстояние слеваcanvas
изimgLeft
.
Мы получили индекс первого отображения изображения ранее, когда мы нажимаем, чтобы увеличить, это не что иное, как добавление единицы к текущему значению индекса, а уменьшение масштаба — это вычитание единицы.
Мы можем пойти в соответствии с новым значением индексамассив измеренийВыносим ширину соответствующего индекса из , и через исходную ширину и высоту изображения можно получить ширину и высоту, которые должны отображаться в равных пропорциях.Наконец, нам нужно только вычислить размер увеличенного изображения.imgTop
а такжеimgLeft
Значение , по сути, может достигать функция:
/**
* 修改当前 图片大小数组中的 索引
* @param curSizeIndex :
*/
public changeCurSizeIndex(curSizeIndex: number) {
let curScaleIndex = curSizeIndex;
if (curScaleIndex > 12) curScaleIndex = 12;
if (curScaleIndex < 0) curScaleIndex = 0;
// 画布宽高,即显示容器宽高
const cWidth = this.cWidth;
const cHeight = this.cHeight;
// 上一次的索引
const prevScaleTimes = this.curScaleIndex;
// 尺寸数组
const EachSizeWidthArray = this.EachSizeWidthArray;
let scaleRadio = 1;
// 这一次宽度与上一次的比值
// 通过这个值能更方便的得到图片宽高
scaleRadio = EachSizeWidthArray[curScaleIndex] / EachSizeWidthArray[prevScaleTimes];
// 当前图片宽高
this.cImgHeight = this.cImgHeight * scaleRadio;
this.cImgWidth = this.cImgWidth * scaleRadio;
// 得到最新的 imgTop
// imgTop 值正负值是根据画布左上角的点,向下为正
this.imgTop = cHeight / 2 - (cHeight / 2 - this.imgTop) * scaleRadio;
// 设置当前 索引值
this.curScaleIndex = curScaleIndex;
// 如果图片没有超过画布的宽和高
if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) {
this.imgTop = (cHeight - this.cImgHeight) / 2;
}
// imgLeft 的计算
this.imgLeft = cWidth / 2 - this.cImgWidth / 2;
// 在图片滑动的时候或者拖动的时候需要用到
this.LongImgTop = this.imgTop;
this.LongImgLeft = this.imgLeft;
// 绘制图片
// ...
}
мероприятие
событие прокрутки
существуетcanvas
Прокрутка картинки посередине фактически пересчитывает картинку.imgTop
а такжеimgLeft
, а затем перекрасить его.
Здесь мы используем событие колеса прокруткиonWheel
Чтобы рассчитать расстояние прокрутки, через объект событияevent
ВверхdeltaX
а такжеdeltaY
Залезайx/y
Расстояние прокрутки по оси.
Здесь следует отметить один момент — обработку граничных значений,
imgTop
Оно не может быть бесконечно большим и малым, а его максимум не может превышать оговоренного нами ранееLONG_IMG_TOP
Это значение, которое мы устанавливаем, равно10px
, минимум может относиться к следующему методу расчета (расчет граничного значения ширины аналогичен, поэтому не приводится)/** * minImgTop:最小的 imgTop 值 * maxImgTop:最大的 imgTop 值 * imgHeight:图片高度 * winHeight:屏幕高度 * footerHeight:底部操作栏高度 * LONG_IMG_TOP:我们设置的一个上下常量 padding */ // 最小肯定是负数 minImgTop = -(imgHeight - (winHeight - footerHeight - LONG_IMG_TOP)) // 最大 maxImgTop = LONG_IMG_TOP
Далее мыcanvas
определить классWheelUpdate
Методы экземпляра, открытые для внешних вызовов,
// src/components/photoGallery/canvas.ts
/**
* 滚轮事件
* @param e wheel 的事件参数
*/
public WheelUpdate(e: any) {
// ...
// 图片显示容器的宽高
const cWidth = this.cWidth;
const cHeight = this.cHeight;
// 如果图片的宽高都小于图片显示容器的宽高就直接返回
if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) {
return;
}
// 如果图片的高度 大于 显示容器的 高度
// 则允许 在 Y 方向上 滑动
if (this.cImgHeight > cHeight) {
// 此值保存当前图片距离容器 imgTop
this.LongImgTop = this.LongImgTop - e.deltaY;
// e.deltaY 向下
if (e.deltaY > 0) {
// 这里做一个极限值的判断
// 具体是我们的算法
if ((-this.LongImgTop) > this.cImgHeight + LONG_IMG_TOP - window.innerHeight + this.footerHeight) {
this.LongImgTop = -(this.cImgHeight + LONG_IMG_TOP - window.innerHeight + this.footerHeight);
}
} else {
// 往上滑的时候,最大值是兼容值 LONG_IMG_TOP
if (this.LongImgTop > LONG_IMG_TOP) {
this.LongImgTop = LONG_IMG_TOP;
}
}
}
// 处理 x 轴上的滚动
// ...
// 赋值 imgTop,imgLeft
this.imgTop = this.LongImgTop;
this.imgLeft = this.LongImgLeft;
// 绘制图片
// ...
}
событие перетаскивания
Нам нужно воспользоваться помощью перетаскивания картинкиonMouseDown
,onMouseMove
,onMouseUp
Три функции событий. По сути, метод работы аналогичен прокрутке картинки, нам нужно вычислить новыйimgTop
а такжеimgLeft
перерисовать картинку, но мы не можем пройти мимоevent
Значение перетаскивания непосредственно получается ниже.Необходимо получить расстояние перетаскивания через разницу между следующим временем и предыдущим временем, а затем вычислитьimgTop
а такжеimgLeft
стоимость,
Во-первых, в свойствах экземпляра вешаем координаты реального времени процесса перетаскивания изображенияcurPos
на, вonMouseDown
Когда выполняется начальное присвоение координат, так что вonMouseMove
В функции мы можем получить начальные координаты нажатия мыши.
// src/components/photoGallery/index.tsx
/**
* 鼠标按下事件
* @param e
* @param instance : 图片预览的实例
*/
const MouseDown = (e: any, instance: any) => {
// 全局 moveFlag 表示拖动是否开始
moveFlag = true;
const { clientX, clientY } = e;
// 给当前预览实例设置初始 x、y 坐标
instance.curPos.x = clientX;
instance.curPos.y = clientY;
// ...
};
/**
* 鼠标抬起事件
*/
const MouseUp = (e: any) => {
moveFlag = false;
};
/**
* 鼠标移动事件
*/
const MouseMove = useCallback((e: any, instance: any) => {
// 直接调用实例下的 MoveCanvas 方法
instance.MoveCanvas(moveFlag, e);
}, [])
Далее рассмотрим наиболее важные методы перетаскиванияMoveCanvas
, мы вычитаем последнее значение координат из значения координат в реальном времени (curPos
Сохраненное значение) для сравнения, получения расстояния скольжения, чтобы мы могли получить последниеimgTop
а такжеimgLeft
Значение, конечно, не забывайте о расчете граничных значений.
// src/components/photoGallery/canvas.ts
/**
* 鼠标拖动的事件
* @param moveFlag : 是否能移动的标志位
* @param e
*/
public MoveCanvas(moveFlag: boolean, e: any) {
// 在拖动情况下才执行拖动逻辑
if (moveFlag) {
// 图片显示容器的宽高
const cWidth = this.cWidth;
const cHeight = this.cHeight;
if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) {
return;
}
// 当前滑动的坐标
const { clientX, clientY } = e;
// 上一次坐标
const curX = this.curPos.x;
const curY = this.curPos.y;
// 处理 Y 轴上的滚动
if (this.cImgHeight > this.cHeight) {
// 此值保存当前图片距离容器 imgTop
this.LongImgTop = this.LongImgTop + (clientY - this.curPos.y);
// 与滚动类似的边界值计算
}
// 处理 x 轴上的滚动
// ...
// 更新实例属性上的 x、y 值
this.curPos.x = clientX;
this.curPos.y = clientY;
// 赋值 imgTop,imgLeft
this.imgTop = this.LongImgTop;
this.imgLeft = this.LongImgLeft;
// 绘制图片
// ...
}
}
Плагин предварительного просмотра закрывается
Мы закрываем плагин предварительного просмотра изображения, когда нажимаем на изображение, но здесь необходимо учитывать, что мы можем перетаскивать изображение. Когда пользователь перетаскивает изображение, нам не нужно закрывать плагин, поэтому нам нужно оценить нажатие мыши пользователем до и после,x/y
Изменилось ли значение координаты, если оно изменилось, то мы не будем выполнять операцию закрытия, иначе плагин предварительного просмотра будет закрыт напрямую.
потому чтоmosueDown
а такжеmouseUp
событие раньше, чемclick
событие, мы устанавливаем бит флагаDoClick
, если положение мыши до и после нажатия не изменилось, этот флагtrue
, то при нажатии на изображение оно будет закрыто напрямую, иначе оно не будет обработано.
// src/components/photoGallery/index.tsx
const MouseDown = (e: any, instance: any) => {
// ...
StartPos.x = clientX;
StartPos.y = clientY;
}
const MouseUp = (e: any) => {
if (e.clientX === StartPos.x && e.clientY === StartPos.y) {
DoClick = true;
} else {
DoClick = false;
}
}
const Click = () => {
if (!DoClick) return;
const { hideModal } = props;
if (hideModal) {
hideModal();
}
}
Другие точки знаний
Когда создается экземпляр класса изображения
Мы уже создали класс изображения для предварительного просмотра, так когда именно нам нужно создать его экземпляр?
Просто слушайте входящиеimgUrl
Когда есть изменение, просто очистите предыдущий экземпляр, и в то же время создать новый плагин, и это будет хорошо.
// src/components/photoGallery/components/Canvas.tsx
const Canvas = (props: Props): JSX.Element => {
// ...
// canvas 的 dom 元素
let canvasRef: any = useRef();
// 存放预览图片实例的变量
let canvasInstance: any = useRef(null);
useEffect((): void => {
if (canvasInstance.current) canvasInstance.current = null;
const canvasNode = canvasRef.current;
canvasInstance.current = new ImgToCanvas(canvasNode, {
imgUrl,
winWidth,
winHeight,
canUseCanvas,
// 图片加载完成钩子
loadingComplete: function(instance) {
props.setImgLoading(false);
props.setCurSize(instance.curScaleIndex);
props.setFixScreenSize(instance.fixScreenSize);
},
});
}, [imgUrl]);
// ...
}
С этим экземпляром изображенияcanvasInstance
, для различных операций с этим изображением предварительного просмотра, таких какувеличить,уменьшитьМы все можем вызывать имеющиеся у него методы, и его можно просто реализовать.
Размер экрана
Когда мы меняем размер экрана, вам нужно изменить размер в соответствии с последними изображениями рендеринга в реальном времени, где мы написали пользовательскийHooks
, экран монитораsize
Изменение.
// src/components/photoGallery/index.tsx
function useWinSize(){
const [ size , setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
const onResize = useCallback(()=>{
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
})
}, []);
useEffect(()=>{
window.addEventListener('resize', onResize, false);
return ()=>{
window.removeEventListener('resize', onResize, false);
}
}, [])
return size;
}
canvas
рисовать мерцание
Есть еще одна проблемаcanvas
В процессе рисования, когда экранresize
Во время процесса будет проблема мерцания, как показано ниже:
Это потому, что при перекрашивании холста нам нужно использоватьclearRect
Чтобы очистить холст, холст в это время пуст, и для начала перерисовки требуется соответствующее время, поэтому в видении будет заставка.Решение заставки на самом деле как решить проблему длительного времени рисования.
мы можем обратиться кдвойной кешконцепция для решения этой проблемы, оставив процесс рисования накеш холст, чтобы на страницеcanvas
Процесс рисования опущен, икеш холстне добавляется на страницу, поэтому мы не можем видеть процесс рисования, втайникcanvas
После рисования назначьте его непосредственно на исходную страницуcanvas
Это решает проблему заставки.
// src/components/photoGallery/canvas.ts
class ImgToCanvas {
// ...
private cacheCanvas : any;
private context : any;
// ...
private drawImg (type?: string) {
// 页面中 canvas
const context = this.context;
// ...
// 创建一个 缓存 canvas,并挂到实例属性 cacheCanvas 下
if (!this.cacheCanvas) {
this.cacheCanvas = document.createElement("canvas");
}
// 设置 缓存 canvas 的宽高
this.cacheCanvas.width = this.cWidth;
this.cacheCanvas.height = this.cHeight;
// 创建画布
const tempCtx = this.cacheCanvas.getContext('2d')!;
// 使用 缓存 canvas 画图
tempCtx.drawImage(image, this.imgLeft, this.imgTop, this.cImgWidth, this.cImgHeight);
// 清除画布,并将缓存 canvas 赋给 页面 canvas
requestAnimationFrame(() => {
this.clearLastCanvas(context);
context.drawImage(this.cacheCanvas, 0, 0);
})
// ...
}
}
резюме
В этой статье организован процесс реализации плагина предварительного просмотра изображений с имитацией чернил с нуля до единицы.Анализ мыслей,Разделение структуры кода,Реализация основной логикиЭти аспекты были проработаны.
При написании этого плагина авторcanvas
рисунокapi
, как бороться сcanvas
Проблема мерцания картинки во время рисования, а дляReact Hooks
Некоторое использование имеет общее понимание.
Честно говоря, я хочу комплимент!