使用 fabric.js 开发移动端 H5 图片编辑器
大家好,我是开源图片编辑器的 https://github.com/ikuaitu/vue-fabric-editor 的作者,它是一款基于 PC 版本的开源图片编辑器。
最近很多开发者咨询,是否可以将开源图片编辑器改造为一款适用于移动端的 H5 版本图片编辑器,最近 H5 版本的图片编辑器刚刚上线,就将实现思路和产品细节整理成笔记分享出来,供大家参考。
基础
开源的图片编辑器的基本功能都有了,例如切换模板、添加元素、自定义字体等,不过相较于移动端的交互会有很大的差异,做了很多改造,这次笔记主要分享一下移动端图片编辑器实现思路和细节。
大纲
- 切换模板
- 添加图片
- 添加组合元素
- 设置背景色
- 修改画布尺寸
- 快捷菜单
- 属性工具条
- 特效字体
- 切换字体
- 输入文字
- 文字排版
- 边框
- 阴影
- 下载图片
注:部分代码示例为封装后的代码,非 fabric.js 原生方法。
1. 切换模板
编辑器基于 fabric.js 开发,所有的模板都是以 json 的格式存储,切换模板只需要请求详情接口,将 json 格式的数据调添加到画布当中即可,需要注意的点是需要将模板中使用的字体名称,并加载字体文件后再进行渲染,否则字体样式没办法正常渲染。
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
即可,另外,经常有图片尺寸大于画布的情况,还需要将图片按画布宽度的一般进行缩放,更方便用户操作。
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 支持将单个元素按照 JSON 格式导出/导入,我们将导出的数据存储在数据库中的,导入时按元素类型导入即可,需要获取 JSON 中元素的类型,并作为方法名调用,同样需要在导入前做字体加载,倒入后做缩放。
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. 设置背景色
设置背景色较为简单,按照 fabric.js 的 API 设置颜色即可,需要注意的是大部分 PC 端的颜色组件并不适配移动端 H5 的场景,不支持 touch 事件,我们使用了 @jaames/iro
这个组件,它在移动端表现出色,完全适配我们的场景,而且它的 API 很灵活,我们将它封装成一个通用的颜色组件,在多处调用。
<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(() => {
// 创建iro.js颜色选择器
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. 修改画布尺寸
日常使用图片编辑器都有修改画布尺寸的需要,在开源项目中已经封装好了相应的方法,直接调用即可,需要注意的是,当修改尺寸弹框弹出时,为了达到所见即所得的效果,要避免弹框遮挡画布,其他属性修改同理。
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. 快捷菜单
很多快捷操作需要能够让用户快速找的并完成操作,我们为元素添加了快捷菜单功能,避免让一些简单的操作让用户在底部菜单栏点来点去,当选中元素时自动展示,取消选中时隐藏即可,需要注意的是在快捷菜单并不总是在元素上方,快捷菜单应该根据元素位置和画布的尺寸进行定位,当菜单超出画布区域时我们要及时调整菜单位置;另外 当属性弹框出现,画布尺寸变化时,需要同步修改菜单位置。
// 更新位置信息
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. 属性工具条
参考了其他图片编辑器,部分属性在点击元素后才会出现可修改选项,取消选中时便隐藏选项,另外 选中的元素不同,可修改选项也不同,这是一个在移动端做复杂图片编辑器中非常棒的一个交互。
我们封装了通用的选中类型和方法,针对每个属性组件单独设置隐藏/展示。
8. 特效字体
特效字体主要是文字元素的颜色、边框、阴影的组合,我们将来文字设置样式后的 JSON 导出并保存在数据库中,当选中某一个特效时,将属性按 JSON 中的数据设置给元素即可。
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. 切换字体
修改字体只需要调用 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 可直接双击文字元素进行修改,不过在移动端这种交互并不醒目,我们单独为文本元素进行了修改,选中元素后,再次点击时弹出输入框,可以在底部菜单栏点击按钮进行修改。
11. 文字排版
文字排版较为简单,我们只需要按照 fabric.js 的文字属性对文字进行属性设置即可,如 fontSize、lineHeight、charSpacing 等。
// 属性值
const baseAttr = reactive({
fontSize: 0,
lineHeight: 0,
charSpacing: 0,
textAlign: '',
fontWeight: '',
fontStyle: '',
underline: false,
linethrough: false,
overline: false,
});
12. 边框
边框样式和文字样式类似,配合颜色组件可以很快捷的实现功能。
// 属性值
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 可以导出 Png/Jpeg/Base64 格式的图片,同时 JPEG 格式还可以指定图片质量与尺寸倍数,详见 fabric.js 的 API 文档。
结尾
以上就是 fabric.js 开发移动端编辑器的实现细节了,结合我们的开源项目和插件化架构可以很方便的完成项目开发,如果你在做类似项目或者做类似的项目,欢迎与我交流。
开源项目:https://github.com/ikuaitu/vue-fabric-editor/blob/main/README-zh.md
使用 fabric.js 开发移动端 H5 图片编辑器的更多相关文章
- 使用JS开发桌面端应用程序NW.js-1-Menu菜单的使用小记
前言 本文主要内容为nw.js官方文档中没有提到,而在实际入手开发过程中才碰到的问题以及经验的汇总. 详情请查看官方文档:http://docs.nwjs.io/en/latest/Reference ...
- 使用JS开发桌面端应用程序NW.js-2-开发问题小记
前言 本文为开发nw中遇到的各种问题,仅以记录供备忘以及遇到相同问题的人的一点点解决思路. 1. package.json中的window字段无效 原因:package.json中的window字段, ...
- 使用JS开发桌面端应用程序NW.js-3-开发问题小记
前言 因为我们的项目是2C的,而XP系统是最大的用户量占比,所以只能使用nw开发而不能用Electron,本文谨记开发nw过程中遇到的各种问题以及解决方案. nw.Window.open打开新窗口不能 ...
- 【转】原生js实现移动端h5长按事件
$("#target").on({ touchstart: function(e) { // 长按事件触发 timeOutEvent = setTimeout(function() ...
- 移动端H5页面高清多屏适配方案
背景 开发移动端H5页面 面对不同分辨率的手机 面对不同屏幕尺寸的手机 视觉稿 在前端开发之前,视觉MM会给我们一个psd文件,称之为视觉稿. 对于移动端开发而言,为了做到页面高清的效果,视觉稿的规范 ...
- [转]:移动端H5页面高清多屏适配方案
原文链接:http://www.tuicool.com/articles/YJviea 背景 开发移动端H5页面 面对不同分辨率的手机 面对不同屏幕尺寸的手机 视觉稿 在前端开发之前,视觉MM会给我们 ...
- 解惑好文:移动端H5页面高清多屏适配方案
背景 开发移动端H5页面 面对不同分辨率的手机 面对不同屏幕尺寸的手机 视觉稿 在前端开发之前,视觉MM会给我们一个psd文件,称之为视觉稿. 对于移动端开发而言,为了做到页面高清的效果,视觉稿的规范 ...
- 基于node.js 的 websocket的移动端H5直播开发
这一篇介绍一下基于node.js 的 websocket的移动端H5直播开发, 下载文章底部的源码,我是用vscode打开, 首先在第一个终端运行 npm run http-server 这个指令是运 ...
- 带你使用h5开发移动端小游戏
带你使用h5开发移动端小游戏 在JY1.x版本中,你要做一个pc端的小游戏,会非常的简单,包括说,你要在低版本的浏览器IE8中,也不会出现明显的卡顿现象,你只需要关心游戏的逻辑就行了,比较适合逻辑较为 ...
- 移动端H5混合开发设置复盘与总结
此篇接上一篇: 移动端H5混合开发,Touch触控,拖拽,长按, 滑屏 实现方案 https://www.cnblogs.com/buoge/p/9346699.html app 场布设置已经上线了, ...
随机推荐
- NIO的三大核心组件详解,充分说明为什么NIO在网络IO中拥有高性能!
一.写在开头 我们在上一篇博文中提到了Java IO中常见得三大模型(BIO,NIO,AIO),其中NIO是我们在日常开发中使用比较多的一种IO模型,我们今天就一起来详细的学习一下. 在传统的IO中, ...
- mybatisplus轻松完成一次模糊+分页查询
之前一直用mybatis+pageinfo完成模糊+分页查询,还需要手写sql语句,之前一直没做尝试,今天试了试mybatisplus一个人完成模糊+分页,挺简单的 有一个小插曲是,我的前端接受的da ...
- Mac端MySQL安装教程
Mac端: 1.安装 ** 首先进入官网:**https://dev.mysql.com/downloads/mysql/ 根据自身情况是M系列芯片(ARM版)还是Intel系列芯片(x ...
- 自动化测试框架开发python selenium excel POM
自动化测试框架开发python selenium excel POM 本文主要是采用python selenium excel POM 做UI自动化测试 测试框架选用的是unittest 测试框架用的 ...
- 【Vue】Re01 理论概念和入门上手
一.Vue概述 什么是渐进式?1.把Vue作应用的一部分嵌套项目中2.如果完全抛弃其他组件和框架,Vue又具有丰富的生态和库莱支持3.Core + Router + VueX 满足项目绝大多数的需求- ...
- iOS开发基础146-深入解析WKWebView
WKWebView是苹果在iOS 8中引入的重要组件,它替代了UIWebView,为开发者提供了高性能.高稳定性的网页显示和交互能力.在本文中,我们将深入探讨WKWebView的底层架构.关键特性.使 ...
- 哈哈哈,我就说未来要研发无人的AI潜艇嘛 —— 说啥来啥 —— AI驱动的无人潜艇
相关: 沉默5个月后,美国对华发出挑战书,万没想到,中方打法早就变了
- git 如何删除一个文件名为nul的文件
前提 当我发现存在一个nul的文件,手动删除/移动它,都会提示ms-dos功能无效或文件过大.想一想这个nul应该是某个保留字,所以普通的方式不能删除 解决方案 https://stackoverfl ...
- 《最新出炉》系列初窥篇-Python+Playwright自动化测试-64 - Canvas和SVG元素推拽
1.简介 今天宏哥分享的在实际测试工作中很少遇到,比较生僻,如果突然遇到我们可能会脑大.懵逼,一时之间不知道怎么办?所以宏哥这里提供一种思路供大家学习和参考. 2.SVG简介 svg也是html5新增 ...
- jQuery的基本操作总结
什么是jquery? 就是一个用js的插件库 解决了原生dom的操作的兼容性和代码量 使用前需要引入它的js库 以下例子以 jQuery1.12.4.js 这个版本为例 一:jQuery入口函数 ...