предисловие
После почти четырех месяцев разработки и тестирования фоторедактор Zanku Hailuo наконец-то выпущен! 👏👏 Интеграция редактора и галереи упрощает проектирование. Первоначальный замысел проекта также очень ясен: вернуть сообществу хороший инструмент дизайна и повысить креативность дизайнерского круга. Текущая версия имеет три функции: обрезка, текст и фильтры, и позже будет продолжена итерация для улучшения взаимодействия с пользователем и расширения функций.
резюме
Весь проект построен вокруг React+Fabric.jsКроме того, Redux используется для управления состоянием для решения сценариев приложений с несколькими взаимодействиями. В то же время есть еще Immutable +Reselect, который используется для повышения производительности всего проекта.
Fabric — мощная библиотека обработки графики, инкапсулированная на основе Canvas, упрощает реализацию различной графики, и в то же время расширяет систему событий, фильтры, перетаскивание, масштабирование, разбор SVG, анимацию и другие функции. Браузеры IE10 и выше. Общий размер сжатого файла составляет около 270 КБ, и официальный также предоставляет (сделанный на заказ), вы можете отфильтровать некоторые функции, чтобы уменьшить размер файла.
Его общая структура выглядит следующим образом:
Холст используется как контейнер, и вся 2D-графика и комбинированные эффекты могут быть размещены на холсте.Класс инструментов предоставляет небольшое количество общедоступных функций.
Согласно официальной документации, для создания экземпляра холста требуется это (холсты ниже всех представляют созданные объекты)
const instance = new fabric.Canvas('c')
Из-за компонентной формы React нам нужно дождаться рендеринга соответствующего компонента, прежде чем создавать его экземпляр, что ограничивает область холста и делает невозможным заполнение 2D-графики в другом месте. Если вы хотите быть глобально доступным внутри, вам нужно немного изменить способ создания экземпляра.
Грубый код выглядит следующим образом:
lib/fabric.js
import fabric from 'fabric'
const instance = new fabric.Canvas() // new Canvas() 实际上调用的是initialize
export { instance }
src/editor.js
import { instance } from 'lib/fabric'
// ...
componentDidMount() {
instance.initialize(this.canvas, {
preserveObjectStacking: true
})
}
render() {
return (
<canvas ref={ref => { this.canvas = ref }} />
)
}
Таким образом, на него можно ссылаться в любом месте проекта.
с реакцией
Чтобы обогатить содержимое холста, вам нужно вызвать instance.add, чтобы добавить другие экземпляры, такие как
const text = new fabric.Text('hello world', { fontSize: 24 })
instance.add(text)
Конечно, это самое основное. Если вы хотите изменить шрифт, цвет, обводку, тень и т. д., вы можете установить это с помощью дополнительных параметров.В настоящее время поддерживаются следующие свойства: ["stroke"
, "strokeWidth"
, "fill"
, "fontFamily"
, "fontSize"
, "fontWeight"
, "fontStyle"
, "underline"
, "overline"
, "linethrough"
, "textBackgroundColor"
]
Способ добавления других инстансов аналогичен, основное отличие заключается в элементах конфигурации инстанса, подробности в официальной документации.
Так как же сохранить состояние работы пользователя? Другими словами, есть ли способ сериализовать содержимое холста в объект?
serializationИменно то, что требуется. Сериализованный холст отражает содержимое текущего холста. Пока вы вызываете toObject каждый раз, когда обновляете холст, вы можете обновлять данные в хранилище.На основе этого можно реализовать отмену, повтор и автоматическое сохранение.
фильтр
Canvasrenderingcontext2d.getimagedata () Возвращает объект imagedata - описать данные пикселей скрытой зоны холста. Его атрибут данных описывает одномерный массив, включающий последовательность данных RGBA с использованием целого числа от 0 до 255 (включительно) на фиг. Ткань построенфильтросновывается наАлгоритм цветовой матрицыбыть реализованным. В частности, каждый фильтр соответствует матрице 4 * 5. Для RGBA в области текущего пикселя после применения матричного алгоритма будет получен новый R'G'B'A'. Таким образом, после вызова putImageData для повторного заполнения холста вы можете увидеть эффект применения фильтра.
запись истории
Для некоторых конкретных действий пользователей мы реализуем отмену и повтор, сохраняя их историю. Как упоминалось выше, холсты можно сериализовать, поэтому также можно реализовать отмену и повтор. В сообществе уже есть относительно зрелая редукция-отмена, которая по сути является редуктором. Хоть в нем и есть функция фильтрации, которая может указать определенные действия, она повлияет на конечную структуру хранилища, что очень неприятно для проектов, использующих библиотеку Reselect. И каждый раз, когда действие запускается, оно будет оцениваться, а затем распределяться на нижний редьюсер, будет определенная потеря производительности. Для двух вышеуказанных пунктов мы внедрили внутри себя класс отмены, который может указывать и записывать изменения ключевых значений ключей в хранилище, не затрагивая структуру хранилища.
Общая ситуация заключается в том, чтобы сначала определить два стека для хранения содержимого отмены и повтора и snapshotFields для хранения значений ключей, которые необходимо записать (например, doc.fabric.data в хранилище)
import { Stack, Map, List, fromJS, is } from 'immutable'
class Snapshot {
undoStack = Stack([])
redoStack = Stack([])
snapshotFields = List([])
takeSnapshot() {
// 取出当前的store
const snapshot = this.getSnapshot()
// 与undoStack栈顶的store做比较,如果不同则放入栈中
const isEqual = is(this.undoStack.peek(), snapshot)
if (!isEqual) {
this.getRedoLength() > 0 && this.resetRedoStack()
this.undoStack = this.undoStack.push(snapshot)
}
}
getSnapshot() {
// 返回store,此处的store是经过过滤的,也就是只有snapshotFields中的字段才会返回
}
loadSnapshot(state) {
/**
* 伪代码如下
* 1. 遍历snapshotFields
* 2. 取出state中对应Field的值
* 3. 更新store中对应的值
*/
}
includeKeyPathInSnapshots(e) {
this.snapshotFields = this.snapshotFields.push(
Array.isArray(e) ? fromJS(e) : e
)
}
undo() {
if (this.undoStack.size > 1) {
const snapshot = this.getSnapshot()
this.redoStack = this.redoStack.push(snapshot)
this.undoStack = this.undoStack.pop()
const peeked = this.undoStack.peek()
this.loadSnapshot(peeked)
}
}
redo() {
if (this.redoStack.size > 0) {
const peeked = this.redoStack.peek()
this.undoStack = this.undoStack.push(peeked)
this.redoStack = this.redoStack.pop()
this.loadSnapshot(peeked)
}
}
// ...
}
С помощью Snapshot после создания экземпляра вы можете указать, какое значение ключа должно быть записано с помощью includeKeyPathInSnapshots. Метод takeSnapshot можно поместить в обратный вызов после обновления холста для записи данных каждого холста, а отмену и повтор можно привязать к соответствующему событию Click компонента, и вся отмена и повтор примерно завершены.
скачать
На данный момент поддерживает загрузку в формате png, jpg. Процесс загрузки показан на рисунке ниже, без бизнес-логики и соответствующих проверок разрешений.
После получения ссылки на исходное изображение и его локальной загрузки, если размер, выбранный пользователем, не соответствует исходному размеру, его необходимо обрезать.pica, поместите собственный элемент холста после обрезки, цель состоит в том, чтобы заменить исходный элемент холста в холсте, а затем применить фильтр (из-за авторских прав на изображение, изображения, отредактированные пользователем, представляют собой все маленькие изображения с водяными знаками, только пользователи krypton могут использовать функцию загрузки), таким образом обрабатывается исходное изображение, которое только что было обрезано. Затем преобразуйте обработанный Canvas в объект Blob, который также требуется в настоящее время.blueimp-canvas-to-blobДля совместимости с некоторыми браузерами, не поддерживающими canvas.toBlob.
Наконец, получите новый объект URL через window.URL.createObjectURL(blob) и назначьте его динамически созданному тегу для завершения загрузки.
const objectURL = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.download = fileName
a.setAttribute('style', 'display: none;')
a.href = objectURL
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
стек технологий
React + React-Router + Redux + Immutable + Reselect + Fabric.js
Эпилог
Адрес опыта:Станция Крутой Герой
автор:cuining