⚠️ Эта статья является первой подписанной статьей сообщества Nuggets, и ее перепечатка без разрешения запрещена.
Введение
Мы знаем, что для повышения эффективности корпоративных исследований и разработок и быстрого реагирования на потребности клиентов многие предприятия в настоящее время приступают к цифровой трансформации, не только крупные фабрики (Alibaba, Byte, Tencent, Baidu) выполняют визуализацию с низким кодом, многие малые и средние Этим занимаются и крупные предприятия, и все больше ценятся программисты с техническим образованием, связанным с визуальным low-code.
В последнее время я занимаюсь визуализацией данных иlowcode/nocode
Соответствующие проекты, адаптированные к моему собственному опыту работы иlowcode/nocode
исследования, а также написал сериювизуализация с низким кодомСерия статей, сегодня мы продолжаем делиться контентом, связанным с визуализацией -Визуальный редактор изображений.
В процессе обмена я возьму в качестве примера Mitu, проект с открытым исходным кодом, который я недавно написал, чтобы тщательно разобрать процесс его реализации. Mitu в основном является второстепенным редактором H5.H5-DooringДля обработки изображений вы также можете легко разрабатывать и расширять его, чтобы стать более мощным редактором изображений.
В конце статьи прикреплюgithubадрес иdemoАдрес удобен для всех, чтобы узнать и испытать. Далее я познакомлю вас с этим редактором изображений с открытым исходным кодом и проанализирую его.Mitu.
Введение в проект
Выше приведены некоторые демонстрационные эффекты редактора изображений.Мы можем быстро создавать нужные изображения путем перетаскивания и реорганизации, а также сохранять изображения в качестве шаблонов для последующего повторного использования. Перед разработкой проекта я также разработал простой прототип, чтобы гарантировать, что мое направление развития не отклонится.
Согласно моему обычному стилю письма, я сначала перечислю схему технической реализации, чтобы каждый мог читать и учиться выборочно и эффективно:
- Визуальный редактор построения проекта и выбора технологии
- Дизайн графической библиотеки
- Дизайн редактора свойств
- Реализация пользовательского контроллера элементов
- Реализация функции предварительного просмотра
- функция сохранения изображения
- Реализация сохранения шаблона
- Реализация функции шаблона импорта
- Пост-планирование визуального редактора изображений
Что ж, без лишних слов, приступим к нашей технической реализации.
Техническая реализация
Строительство проекта и выбор технологии
Идея реализации редактора не имеет ничего общего со стеком технологий, здесь я используюReact
Для достижения, конечно, если вы предпочитаетеVue
илиsveltejs
, проблем нет, общий технический отбор проекта таков:
- umiМасштабируемая среда корпоративных интерфейсных приложений
- React + Typescript
- AntdБиблиотека интерфейсных компонентов
-
fabricможно упростить
Canvas
библиотека программирования - localStorageлокальное хранилище данных
Конечно, в процессе реализации проекта есть еще много деталей и идей, и я буду знакомить вас с ними по порядку. Если все правыfabricНе волнуйтесь, если вы не знакомы с этой библиотекой, я познакомлю вас с этой библиотекой через реализацию конкретных функций.
Прежде чем представить следующий контент, давайте сначала установим егоfabric, а затем инициализируйте холст.
yarn add fabric
Инициализировать холст:
import { fabric } from "fabric";
import { nanoid } from 'nanoid';
import { useEffect, useState, useRef } from 'react';
export default function IndexPage() {
const canvasRef = useRef<any>(null);
useEffect(() => {
canvasRef.current = new fabric.Canvas('canvas');
// 创建一个文本元素
const shape = new fabric.IText(nanoid(8), {
text: 'H5-Dooring',
width : 60,
height : 60,
fill : '#06c',
left: 30,
top: 30
})
// 将文本元素插入画布
canvasRef.current.add(shape);
// 设置画布的背景色
canvasRef.current.backgroundColor = 'rgba(255,255,255,1)';
})
return <canvas id="canvas" width={600} height={400}></canvas>
}
Таким образом, мы создали холст и вставили в него фрагмент редактируемого и перетаскиваемого текста следующим образом:
Дизайн графической библиотеки
Как редактор изображений, чтобы повысить гибкость использования, нам также необходимо предоставить некоторую базовую графику, чтобы облегчить нам создание изображений, поэтому я добавил в редактор графическую библиотеку:
в основном кактекст,картина,прямая линия,прямоугольник,круглый,треугольник,стрела,мозаика, конечно, вы можете добавить больше базовых примитивов в соответствии с вашими потребностями. Мы нажимаем на любой элемент в библиотеке изображений, чтобы вставить его на холст.fabricизadd
метод, конечноfabricТакже имеется много встроенной базовой графики, на которую мы можем ссылаться в документации. Чтобы сделать вставку графики более инкапсулированной, я определяю основныеschema
структура:
const baseShapeConfig = {
IText: {
text: 'H5-Dooring',
width : 60,
height : 60,
fill : '#06c'
},
Triangle: {
width: 100,
height: 100,
fill: '#06c'
},
Circle: {
radius: 50,
fill: '#06c'
},
Rect: {
width : 60,
height : 60,
fill : '#06c'
},
Line: {
width: 100,
height: 1,
fill: '#06c'
},
Arrow: {},
Image: {},
Mask: {}
}
Таким образом, наш метод вставки графики можно записать следующим образом:
type ElementType = 'IText' | 'Triangle' | 'Circle' | 'Rect' | 'Line' | 'Image' | 'Arrow' | 'Mask'
const insertShape = (type:ElementType) => {
shape = new fabric[type]({
...baseShapeConfig[type],
left: size[0] / 3,
top: size[1] / 3
})
canvasRef.current.add(shape);
}
Позже, когда мы добавим графику, нам нужно будет только определитьschema
Да, но следует отметить, чтоfabricСпособ создания графики не у всех одинаков, нам нужны специальные суждения для создания конкретных рисунков, таких как прямой путь:
if(type === 'Line') {
shape = new fabric.Path('M 0 0 L 100 0', {
stroke: '#ccc',
strokeWidth: 2,
objectCaching: false,
left: size[0] / 3,
top: size[1] / 3
})
}
Конечно, мы также можем использоватьswitch
Чтобы по-разному обрабатывать разные ситуации, мы внедрили базовую библиотеку изображений.
Дизайн редактора свойств
Редактор свойств в основном используется для настройки графических свойств, таких какЦвет заливки,цвет обводки,ширина штриха, в настоящее время я в основном определяю эти три измерения, вы также можете продолжать расширять дополнительные редактируемые атрибуты на основе этого, аналогичноH5-DooringПанель настройки свойств компонента.
В правой части редактора мы можемредактирование атрибутовОбласть управляет атрибутами графики. Поскольку в настоящее время есть только 3 атрибута, я просто жестко закодировал и написал их. Вы также можете использовать динамический рендеринг для достижения этого. Следует отметить, как мы узнаем, какой компонент мы выбрали?fabricпредоставляет рядapiПомогите нам лучше контролировать объект элемента, здесь мы используемgetActiveObject
Метод получает текущий выбранный элемент, а конкретный код реализации выглядит следующим образом:
// ...
// 定义基础属性
const [attrs, setAttrs] = useState({
fill: '#0066cc',
stroke: '',
strokeWidth: 0,
})
// 更新选中的元素
const updateAttr = (type: 'fill' | 'stroke' | 'strokeWidth' | 'imgUrl', val:string | number) => {
setAttrs({...attrs, [type]: val})
// 获取当前选中元素对象
const obj = canvasRef.current.getActiveObject()
// 设置元素属性
obj.set({...attrs})
// 重新渲染
canvasRef.current.renderAll();
}
Я не буду здесь представлять стильную реализацию редактора свойств, все они относительно просты, давайте взглянем на базовую структуру элемента редактирования:
<span className={styles.label}>描边宽度: </span>
<InputNumber size="small" min={0} value={attrs.strokeWidth} onChange={(v) => updateAttr('strokeWidth', v)} />
Реализация пользовательского контроллера элементов
потому что по умолчаниюfabricНет кнопки удаления и логики, поэтому нам нужно дважды расшириться, как раз вовремя.fabricПредоставляет настраиваемый метод расширения. Далее давайте настроим кнопку удаления и реализуем логику удаления.
Конкретный код реализации выглядит следующим образом:
// 删除按钮
const deleteIcon = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E";
// 删除方法
function deleteObject(eventData, transform) {
const target = transform.target;
const canvas = target.canvas;
canvas.remove(target);
canvas.requestRenderAll();
}
// 渲染icon
function renderIcon(ctx, left, top, styleOverride, fabricObject) {
const size = this.cornerSize;
ctx.save();
ctx.translate(left, top);
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
ctx.drawImage(img, -size/2, -size/2, size, size);
ctx.restore();
}
// 全局添加删除按钮
fabric.Object.prototype.controls.deleteControl = new fabric.Control({
x: 0.5,
y: -0.5,
offsetY: -32, // 自定义距元素的偏移距离, 也可以定义offsetX
cursorStyle: 'pointer',
mouseUpHandler: deleteObject,
render: renderIcon,
cornerSize: 24
});
Таким образом, мы реализовали настраиваемый элемент управления, и мы также можем реализовать настраиваемые элементы управления аналогичным образом. Эффект следующий:
Реализация функции предварительного просмотра
Функция предварительного просмотра, которую я в основном использую нативнуюcanvasизtoDataURL
метод созданияbase64
данные, а затем назначьте ихimg
Этикетка.还有一个细节需要注意的是如果我们在预览之前画布仍然有选中状态的元素,那么控制点也会被截取出来,如下:
Это очень плохо для пользовательского опыта. Нам нужно видеть чистую картинку при предварительном просмотре. Мое решение - отменить выбранное состояние всех элементов холста перед предварительным просмотром. Вы можете использоватьfabricпримерdiscardActiveObject()
Метод деактивирует состояние активации, а затем обновляет холст.Конкретная логика реализации выглядит следующим образом:
// 1. 取消画布所有元素的选中状态
canvasRef.current.discardActiveObject()
canvasRef.current.renderAll();
// 2. 将当前画布转化为图片的base64地址
const img = document.getElementById("canvas");
const src = (img as HTMLCanvasElement).toDataURL("image/png");
// 3. 设置元素url,显示预览弹窗
setImgUrl(src)
setIsShow(true)
Отображение эффекта предварительного просмотра:
функция сохранения изображения
Сохранение изображений на самом деле очень похоже на функцию предварительного просмотра, с той лишь разницей, что нам нужно загружать изображения на локальный компьютер, поэтому я в основном использую метод чистого внешнего интерфейса для загрузки изображений, вы также можете использовать внешний вид. закончите загрузку схемы, с которой вы знакомы, а затем вставьте меня. Реализация программы:
function download(url:string, filename:string, cb?:Function) {
return fetch(url).then(res => res.blob().then(blob => {
let a = document.createElement('a');
let url = window.URL.createObjectURL(blob);
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
cb && cb()
}))
}
в основном используетсяwindow
изURL
объектcreateObjectURL
а такжеrevokeObjectURL
метод, я также поделился соответствующей реализацией в своей статье два года назад, вы можете сослаться на нее, если вам интересно. Эффект загрузки следующий:
Реализация сохранения шаблона
В процессе разработки редактора изображений мы также должны учитывать сохранение ресурсов пользователя.Например, хорошо сделанное изображение может быть сохранено в качестве шаблона для повторного использования в следующий раз, поэтому я также реализовал простое сохранение и использование шаблона в редакторе. , функция. Давайте сначала посмотрим на эффект:
В демоверсии мы видим, что после сохранения в качестве шаблона он будет автоматически синхронизирован со списком шаблонов слева, и мы можем напрямую импортировать шаблон для вторичного создания при следующем создании. Ниже представлена логическая схема реализации:
Из рисунка выше видно, что мы сохраняем шаблон не только для сохранения картинки, но и для сохранения соответствующего изображения.json schemaданные, зачем сохранятьjson schemaЭто делается для того, чтобы каждый элемент шаблона мог быть восстановлен после того, как пользователь переключится на соответствующий шаблон, аналогичный тому, с которым мы наиболее знакомы.PSD
Исходный файл.fabricПредоставляет метод для сериализации холстаtoDatalessJSON()
, когда мы сохраняем шаблон, нам нужно только сериализоватьjson
И изображения можно сохранять вместе, чтобы облегчить процесс там, где я существую.localStorage, вы также можете использовать локализованное хранилище большой емкостиindexedDB
, на котором я также ранее основывалсяindexedDB
Инкапсулирует стандартную библиотеку кэшированияxdb, вы можете использовать его напрямую.
Конкретная реализация сохранения шаблона выглядит следующим образом:
const handleSaveTpl = () => {
const val = tplNameRef.current.state.value
const json = canvasRef.current.toDatalessJSON()
const id = nanoid(8)
// 存json
const tpls = JSON.parse(localStorage.getItem('tpls') || "{}")
tpls[id] = {json, t: val};
localStorage.setItem('tpls', JSON.stringify(tpls))
// 存图片
canvasRef.current.discardActiveObject()
canvasRef.current.renderAll()
const imgUrl = getImgUrl()
const tplImgs = JSON.parse(localStorage.getItem('tplImgs') || "{}")
tplImgs[id] = imgUrl
localStorage.setItem('tplImgs', JSON.stringify(tplImgs))
// 更新模版列表
setTpls((prev:any) => [...prev, {id, t: val}])
setIsTplShow(false)
}
Реализация функции шаблона импорта
Суть импорта шаблонов в десериализацииJson Schema, в исследованииfabricВ процессе было установлено, что его можно загружать напрямуюjson
Визуализируйте графическую последовательность, чтобы мы могли напрямую сохранить приведенное выше.json
Загрузить прямо на холст:
// 1.加载前清空画布
canvasRef.current.clear();
// 2.重置画布背景色
canvasRef.current.backgroundColor = 'rgba(255,255,255,1)';
// 3. 渲染json
canvasRef.current.loadFromJSON(tpls[id].json, canvasRef.current.renderAll.bind(canvasRef.current))
Затем мы можем динамически переключать шаблоны в соответствии с сохраненным списком шаблонов:
Позднее планирование
У меня есть этот редактор изображенийgithub
Открытый исходный код, вы можете разработать более мощный редактор изображений на основе этого времени.Для последующего планирования редактора изображений я также оценил несколько возможных направлений.Если вы заинтересованы, вы также можете связаться со мной для участия в проекте.
Дальнейшее планирование выглядит следующим образом:
- Отменить повторить
- Настройки фона холста
- Богатая библиотека графических компонентов
- Конфигурация фильтра изображения
- Модульный интерфейс
- Разобрать PSD
Если вы заинтересованы в визуальном конструировании или low-code/zero-code, вы также можете обратиться к моим предыдущим статьям или обменяться мыслями и опытом в области комментариев.Добро пожаловать, чтобы вместе изучить настоящую фронтенд-технологию.
github: mitu-editor | Легкое и масштабируемое решение для редактирования изображений/графики
Стартер:Технологическое сообщество Nuggets
автор:Сюй Сяоси
Столбец:визуализация с низким кодом
Официальный аккаунт: Интересный интерфейс разговора
Прошлые статьи
- Как спроектировать хранилище компонентов для платформы визуального построения?
- Проектирование механизма визуализации большого экрана с нуля
- Используйте электрон для создания настольного визуального редактора Dooring с нуля
- (Low Code) Платформа визуализации Анализ дизайна источника данных
- Создайте редактор страниц для ПК PC-Dooring с нуля
- Как быстро разработать страницы H5 со строительными блоками?