大家好,我是开源图片编辑器的 https://github.com/ikuaitu/vue-fabric-editor 的作者,它是一款基于 PC 版本的开源图片编辑器。

最近很多开发者咨询,是否可以将开源图片编辑器改造为一款适用于移动端的 H5 版本图片编辑器,最近 H5 版本的图片编辑器刚刚上线,就将实现思路和产品细节整理成笔记分享出来,供大家参考。

基础

开源的图片编辑器的基本功能都有了,例如切换模板、添加元素、自定义字体等,不过相较于移动端的交互会有很大的差异,做了很多改造,这次笔记主要分享一下移动端图片编辑器实现思路和细节





大纲

  1. 切换模板
  2. 添加图片
  3. 添加组合元素
  4. 设置背景色
  5. 修改画布尺寸
  6. 快捷菜单
  7. 属性工具条
  8. 特效字体
  9. 切换字体
  10. 输入文字
  11. 文字排版
  12. 边框
  13. 阴影
  14. 下载图片

注:部分代码示例为封装后的代码,非 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 图片编辑器的更多相关文章

  1. 使用JS开发桌面端应用程序NW.js-1-Menu菜单的使用小记

    前言 本文主要内容为nw.js官方文档中没有提到,而在实际入手开发过程中才碰到的问题以及经验的汇总. 详情请查看官方文档:http://docs.nwjs.io/en/latest/Reference ...

  2. 使用JS开发桌面端应用程序NW.js-2-开发问题小记

    前言 本文为开发nw中遇到的各种问题,仅以记录供备忘以及遇到相同问题的人的一点点解决思路. 1. package.json中的window字段无效 原因:package.json中的window字段, ...

  3. 使用JS开发桌面端应用程序NW.js-3-开发问题小记

    前言 因为我们的项目是2C的,而XP系统是最大的用户量占比,所以只能使用nw开发而不能用Electron,本文谨记开发nw过程中遇到的各种问题以及解决方案. nw.Window.open打开新窗口不能 ...

  4. 【转】原生js实现移动端h5长按事件

    $("#target").on({ touchstart: function(e) { // 长按事件触发 timeOutEvent = setTimeout(function() ...

  5. 移动端H5页面高清多屏适配方案

    背景 开发移动端H5页面 面对不同分辨率的手机 面对不同屏幕尺寸的手机 视觉稿 在前端开发之前,视觉MM会给我们一个psd文件,称之为视觉稿. 对于移动端开发而言,为了做到页面高清的效果,视觉稿的规范 ...

  6. [转]:移动端H5页面高清多屏适配方案

    原文链接:http://www.tuicool.com/articles/YJviea 背景 开发移动端H5页面 面对不同分辨率的手机 面对不同屏幕尺寸的手机 视觉稿 在前端开发之前,视觉MM会给我们 ...

  7. 解惑好文:移动端H5页面高清多屏适配方案

    背景 开发移动端H5页面 面对不同分辨率的手机 面对不同屏幕尺寸的手机 视觉稿 在前端开发之前,视觉MM会给我们一个psd文件,称之为视觉稿. 对于移动端开发而言,为了做到页面高清的效果,视觉稿的规范 ...

  8. 基于node.js 的 websocket的移动端H5直播开发

    这一篇介绍一下基于node.js 的 websocket的移动端H5直播开发, 下载文章底部的源码,我是用vscode打开, 首先在第一个终端运行 npm run http-server 这个指令是运 ...

  9. 带你使用h5开发移动端小游戏

    带你使用h5开发移动端小游戏 在JY1.x版本中,你要做一个pc端的小游戏,会非常的简单,包括说,你要在低版本的浏览器IE8中,也不会出现明显的卡顿现象,你只需要关心游戏的逻辑就行了,比较适合逻辑较为 ...

  10. 移动端H5混合开发设置复盘与总结

    此篇接上一篇: 移动端H5混合开发,Touch触控,拖拽,长按, 滑屏 实现方案 https://www.cnblogs.com/buoge/p/9346699.html app 场布设置已经上线了, ...

随机推荐

  1. 2023 NOIP 游记

    \(\text{Day -INF}\) 提高 \(135\) 卡线进 \(\text{NOIP}\). 集训两天成绩:\(50 \to 135\). \(\text{Day 1}\) 开赛 \(13\ ...

  2. AppiumDesktop控制手机和安卓模拟器

    前言: 本期内容 如何用AppiumDesktop连接安卓手机和安卓模拟器 AppiumDesktop基本参数的获取方法,及如何驱动安卓设备 AppiumDesktop在模拟登陆和爬虫中用到的基本功能 ...

  3. oauth2协议

    什么是OAUTH2协议: 首先是几个概念问题: 资源:用户信息,在微信中存储 资源拥有者:用户 认证服务:微信负责认证用户的身份,也负责为客户端颁发令牌 客户端:携带令牌请求微信获取用户信息 仍以微信 ...

  4. T800机器人图片 —— 强大的好莱坞电影工业,T800机器人模型也如此精细真实!

    视频地址: https://www.ixigua.com/6764744689003266571

  5. php 开发日常收获

    最近项目需求: 仿制某网站的菜单功能 效果如下: 效果大概就是这样啦.分析下吧:主要是分级查询我的思路:首先从数据库中查询所有的顶级目录: 目前就是这几个顶级目录在数据库中特点就是 父级id是0(一般 ...

  6. Two Sided Cards 题解

    前言 五一网课的例题,但是网上没有详细的题解(真的连题解都找不到啊),所以来写一篇,就当攒 RP 了.题目可以在这里提交.原题是 TopCoder - 10947,但是有了账号也交不了? 题目简述 有 ...

  7. 恭喜又一白鲸开源成员成为 Apache SeaTunnel PMC Member

    个人简介 王海林 白鲸开源研发工程师 GitHub ID:hailin0 做过性能监控.数据开发平台等,目前聚焦在数据集成同步及其周边生态的研发 问:作为白鲸开源的一员,您为社区做出过哪些贡献?具体方 ...

  8. JavaFx加载本地工程图片报错

    原因有两点: 1.png或者其他格式的图片文件没有放在src的直接目录下 2.路径填错,多了个符号等 正确姿势应该是 package com.fubao.tools.fx.Stage; import ...

  9. 使用 crontab 设置 Homebrew 自动更新

    本人有强迫症,希望自己电脑上安装的软件永远是最新的.App Store 有自动更新功能,然而 Homebrew 则没有自动更新的选项.每次手动更新的话时间长了又感觉麻烦.后来发现可以使用 cronta ...

  10. Windows10使用MSYS2和VS2019编译FFmpeg详解

    1 环境准备 1.1 安装Visual Studio 2019 这个步骤相对比较简单,不再详细说明. 1.2 安装msys2 首先需要安装msys2环境以及相关的编译依赖项, 官方网址为: https ...