大家好,我是開源圖片編輯器的 https://github.com/ikuaitu/vue-fabric-editor 的作者,它是一款基于 PC 版本的開源圖片編輯器。
最近很多開發(fā)者咨詢,是否可以將開源圖片編輯器改造為一款適用于移動(dòng)端的 H5 版本圖片編輯器,最近 H5 版本的圖片編輯器剛剛上線,就將實(shí)現(xiàn)思路和產(chǎn)品細(xì)節(jié)整理成筆記分享出來,供大家參考。
基礎(chǔ)
開源的圖片編輯器的基本功能都有了,例如切換模板、添加元素、自定義字體等,不過相較于移動(dòng)端的交互會(huì)有很大的差異,做了很多改造,這次筆記主要分享一下移動(dòng)端圖片編輯器實(shí)現(xiàn)思路和細(xì)節(jié)。
大綱
- 切換模板
- 添加圖片
- 添加組合元素
- 設(shè)置背景色
- 修改畫布尺寸
- 快捷菜單
- 屬性工具條
- 特效字體
- 切換字體
- 輸入文字
- 文字排版
- 邊框
- 陰影
- 下載圖片
注:部分代碼示例為封裝后的代碼,非 fabric.js 原生方法。
1. 切換模板
編輯器基于 fabric.js 開發(fā),所有的模板都是以 json 的格式存儲(chǔ),切換模板只需要請求詳情接口,將 json 格式的數(shù)據(jù)調(diào)添加到畫布當(dāng)中即可,需要注意的點(diǎn)是需要將模板中使用的字體名稱,并加載字體文件后再進(jìn)行渲染,否則字體樣式?jīng)]辦法正常渲染。
const loadInfo = async (res: any) => {
const info = res.data
templName.value = info.name;
await canvasEditor.getFontList(JSON.stringify(info.json));
canvasEditor.loadJSON(JSON.stringify(info.json), () => LoadingPlugin(false));
};
2. 添加圖片
fabric.js 中添加圖片提供了很多種方法,我們使用通過最簡單的fabric.Image.fromURL
即可,另外,經(jīng)常有圖片尺寸大于畫布的情況,還需要將圖片按畫布寬度的一般進(jìn)行縮放,更方便用戶操作。
const toEditor = async (e: MouseEvent) => {
visible.value = false
LoadingPlugin(true)
const item = await canvasEditor.createImgByElement(e.target as HTMLImageElement)
await canvasEditor.addBaseType(item, { scale: true })
LoadingPlugin(false)
}
3. 添加組合元素
fabric.js 支持將單個(gè)元素按照 JSON 格式導(dǎo)出/導(dǎo)入,我們將導(dǎo)出的數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫中的,導(dǎo)入時(shí)按元素類型導(dǎo)入即可,需要獲取 JSON 中元素的類型,并作為方法名調(diào)用,同樣需要在導(dǎo)入前做字體加載,倒入后做縮放。
const capitalizeFirstLetter = (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1);
}
const toEditor = async (item: ItemProps) => {
visible.value = false
LoadingPlugin(true)
await canvasEditor.downFontByJSON(JSON.stringify(item.json));
const el = JSON.parse(JSON.stringify(item.json));
const elType = capitalizeFirstLetter(el.type);
new fabric[elType].fromObject(el, (fabricEl: fabric.Object) => {
canvasEditor.dragAddItem(fabricEl);
LoadingPlugin(false)
});
}
4. 設(shè)置背景色
設(shè)置背景色較為簡單,按照 fabric.js 的 API 設(shè)置顏色即可,需要注意的是大部分 PC 端的顏色組件并不適配移動(dòng)端 H5 的場景,不支持 touch 事件,我們使用了 @jaames/iro
這個(gè)組件,它在移動(dòng)端表現(xiàn)出色,完全適配我們的場景,而且它的 API 很靈活,我們將它封裝成一個(gè)通用的顏色組件,在多處調(diào)用。
<template>
<div ref="pickerContainer">
</div>
</template>
<script setup lang="ts">
import iro from '@jaames/iro';
const emit = defineEmits(['update:modelValue', 'change']);
const props = defineProps({
modelValue: {
type: String,
default: '#000000'
},
width: {
type: Number,
default: 200
}
});
const pickerContainer = ref<HTMLButtonElement | string>('');
let colorPicker: any = null;
onMounted(() => {
colorPicker = iro.ColorPicker(pickerContainer.value, {
width: props.width,
color: props.modelValue,
borderWidth: 1,
borderColor: "#fff",
layoutDirection: 'horizontal',
layout: [
{
component: iro.ui.Slider,
options: {
id: 'hue-slider',
sliderType: 'hue'
}
},
{
component: iro.ui.Box,
},
{
component: iro.ui.Slider,
options: {
sliderType: 'alpha'
}
}
]
});
colorPicker.on('color:change', (color: any) => {
const rgbaString = color.rgbaString;
emit('update:modelValue', rgbaString);
emit('change', rgbaString);
});
});
</script>
5. 修改畫布尺寸
日常使用圖片編輯器都有修改畫布尺寸的需要,在開源項(xiàng)目中已經(jīng)封裝好了相應(yīng)的方法,直接調(diào)用即可,需要注意的是,當(dāng)修改尺寸彈框彈出時(shí),為了達(dá)到所見即所得的效果,要避免彈框遮擋畫布,其他屬性修改同理。
const resizeEditor = async () => {
await nextTick()
const editorWorkspase = document.querySelector('#workspace') as HTMLElement
const popElement = document.querySelector('.my-editor-popup') as HTMLElement
const headerElement = document.querySelector('.t-navbar') as HTMLElement
if (popElement) {
editorWorkspase.style.height = `calc(100vh - ${popElement?.offsetHeight + headerElement?.offsetHeight || 0}px)`
} else {
editorWorkspase.style.height = ''
}
}
6. 快捷菜單
很多快捷操作需要能夠讓用戶快速找的并完成操作,我們?yōu)樵靥砑恿丝旖莶藛喂δ埽苊庾屢恍┖唵蔚牟僮髯層脩粼诘撞坎藛螜邳c(diǎn)來點(diǎn)去,當(dāng)選中元素時(shí)自動(dòng)展示,取消選中時(shí)隱藏即可,需要注意的是在快捷菜單并不總是在元素上方,快捷菜單應(yīng)該根據(jù)元素位置和畫布的尺寸進(jìn)行定位,當(dāng)菜單超出畫布區(qū)域時(shí)我們要及時(shí)調(diào)整菜單位置;另外 當(dāng)屬性彈框出現(xiàn),畫布尺寸變化時(shí),需要同步修改菜單位置。
const upDatePosition = async () => {
const activeObject = canvasEditor.canvas.getActiveObject();
if (activeObject) {
canvasEditor.canvas.renderAll();
fixLeft.value = 10;
fixTop.value = 10;
await nextTick();
isIncluded(activeObject);
await nextTick();
}
}
getObjectAttr(upDatePosition)
canvasEditor.canvas.on('selection:updated', upDatePosition)
canvasEditor.canvas.on('mouse:move', upDatePosition)
canvasEditor.on('workspaceAutoEvent', upDatePosition)
7. 屬性工具條
參考了其他圖片編輯器,部分屬性在點(diǎn)擊元素后才會(huì)出現(xiàn)可修改選項(xiàng),取消選中時(shí)便隱藏選項(xiàng),另外 選中的元素不同,可修改選項(xiàng)也不同,這是一個(gè)在移動(dòng)端做復(fù)雜圖片編輯器中非常棒的一個(gè)交互。
我們封裝了通用的選中類型和方法,針對每個(gè)屬性組件單獨(dú)設(shè)置隱藏/展示。
8. 特效字體
特效字體主要是文字元素的顏色、邊框、陰影的組合,我們將來文字設(shè)置樣式后的 JSON 導(dǎo)出并保存在數(shù)據(jù)庫中,當(dāng)選中某一個(gè)特效時(shí),將屬性按 JSON 中的數(shù)據(jù)設(shè)置給元素即可。
const setStyle = (item: ImgItem) => {
const activeObject = canvasEditor.canvas.getActiveObjects()[0];
if (activeObject) {
const values = toRaw(item.json);
const keys = ['fill', 'stroke', 'strokeWidth', 'shadow', 'strokeLineCap'];
activeObject.set('paintFirst', 'stroke');
keys.forEach((key) => {
activeObject.set(key, values[key]);
if (key === 'fill' && typeof values[key] != 'string') {
activeObject.set(key, new fabric.Gradient(values[key]));
}
});
canvasEditor.canvas.renderAll();
}
};
9. 切換字體
修改字體只需要調(diào)用 fabric.js 元素的fontFamily
屬性即可,在修改之前要確保字體加載完成。
const changeCommon = async (key: string, value: any) => {
const activeObject = canvasEditor.canvas.getActiveObjects()[0];
if (activeObject) {
LoadingPlugin(true);
baseAttr.fontFamily = value;
try {
await canvasEditor.loadFont(value)
} catch (error) {
console.log(error)
}
LoadingPlugin(false);
activeObject && activeObject.set(key, value);
canvasEditor.canvas.renderAll();
}
};
10. 輸入文字
fabric.js 可直接雙擊文字元素進(jìn)行修改,不過在移動(dòng)端這種交互并不醒目,我們單獨(dú)為文本元素進(jìn)行了修改,選中元素后,再次點(diǎn)擊時(shí)彈出輸入框,可以在底部菜單欄點(diǎn)擊按鈕進(jìn)行修改。
11. 文字排版
文字排版較為簡單,我們只需要按照 fabric.js 的文字屬性對文字進(jìn)行屬性設(shè)置即可,如 fontSize、lineHeight、charSpacing 等。
const baseAttr = reactive({
fontSize: 0,
lineHeight: 0,
charSpacing: 0,
textAlign: '',
fontWeight: '',
fontStyle: '',
underline: false,
linethrough: false,
overline: false,
});
12. 邊框
邊框樣式和文字樣式類似,配合顏色組件可以很快捷的實(shí)現(xiàn)功能。
const baseAttr = reactive({
stroke: '#fff',
strokeWidth: 0,
strokeDashArray: [],
});
13. 陰影
引用屬性主要是元素的 shadow 子屬性的修改,代碼如下:
const baseAttr = reactive({
shadow: {
color: '#fff',
blur: 0,
offsetX: 1,
offsetY: 1,
}
});
const changeCommon = () => {
const activeObject = canvasEditor.canvas.getActiveObjects()[0];
if (activeObject) {
activeObject.set('shadow', new fabric.Shadow(baseAttr.shadow));
canvasEditor.canvas.renderAll();
}
};
14. 下載圖片
fabric.js 可以導(dǎo)出 Png/Jpeg/Base64 格式的圖片,同時(shí) JPEG 格式還可以指定圖片質(zhì)量與尺寸倍數(shù),詳見 fabric.js 的 API 文檔。
結(jié)尾
以上就是 fabric.js 開發(fā)移動(dòng)端編輯器的實(shí)現(xiàn)細(xì)節(jié)了,結(jié)合我們的開源項(xiàng)目和插件化架構(gòu)可以很方便的完成項(xiàng)目開發(fā),如果你在做類似項(xiàng)目或者做類似的項(xiàng)目,歡迎與我交流。
開源項(xiàng)目:https://github.com/ikuaitu/vue-fabric-editor/blob/main/README-zh.md
?轉(zhuǎn)自https://www.cnblogs.com/nihaojob/p/18426386
該文章在 2024/12/10 9:03:32 編輯過