包阅导读总结
1. `Fabric.js`、`H5页面`、`React`、`图形处理`、`动画效果`
2. 本文主要介绍了使用Fabric.js生成H5页面(React版),包括Fabric.js的简介及优势,实现多个页面切换,生成页面和元素缩略图,设置页面滤镜,添加元素动画和点击事件,以及最终效果展示。
3.
– Fabric.js简介
– 是革命性的JavaScript库,简化HTML5 canvas元素图形处理与交互设计工作。
– 直观对象模型,支持多层画布管理、多种输入方式和格式导入导出。
– 学习曲线平缓,有丰富教程和文档资源。
– 多个页面切换
– 本质上canvas始终一个,数据存储使用Mobx,对pageList进行操作。
– 通过获取选中页面canvas数据并用loadFromJSON加载实现切换。
– 页面生成缩略图
– 使用存储的画布数据生成等比缩放的小型canvas,监听数据变化重新渲染。
– 元素生成缩略图
– 获取当前页面全部元素并转化为对象,调用toDataURL方法获取图片。
– 设置页面滤镜
– 靠css样式设置,Fabric.js能否整体页面设置滤镜未知。
– 给元素添加动画
– 支持动画效果,新建元素默认添加animateList存放动画。
– 封装动画逻辑,修改配置文件可新增动画。
– 给元素添加点击事件
– 原理和添加动画类似,在pc端记录点击事件类型,H5处理。
– 最终效果
– 创建多个页面,添加动画效果和触发事件,效果不错。
思维导图:
文章地址:https://juejin.cn/post/7407034973450534912
文章来源:juejin.cn
作者:洞窝技术
发布时间:2024/8/26 12:17
语言:中文
总字数:3683字
预计阅读时间:15分钟
评分:82分
标签:前端开发,交互设计,Fabric.js,React,Mobx
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
Fabric.js生成H5页面(React版)
1、Fabric.js 简介
Fabric.js 是一个革命性的JavaScript库,它极大地简化了HTML5 canvas元素上的图形处理与交互设计工作。通过提供直观且功能丰富的API,Fabric.js让开发者能够以前所未有的轻松方式在网页上绘制、编辑、管理和动画化各种图形与图像。
这个库的核心优势在于其直观的对象模型,它让canvas上的每一个图形元素(如矩形、圆形、文本、图像等)都成为了可交互的对象。这意味着你不仅可以轻松绘制它们,还能对它们进行拖动、缩放、旋转、变形等复杂的操作,而无需深入底层的canvas绘图API。
Fabric.js还支持多层画布管理,让开发者能够轻松组织复杂的图形结构,通过图层的叠加与切换实现丰富的视觉效果。此外,它还内置了丰富的事件处理机制,能够响应鼠标、键盘和触摸等多种输入方式,使得图形与用户之间的交互更加自然流畅。
更令人称道的是,Fabric.js支持从多种格式(如SVG、JSON和图片文件)导入图形对象,并能够将绘制结果导出为相同或不同的格式。这一特性极大地提升了图形数据的共享与复用能力,为跨平台、跨应用的图形设计提供了极大便利。
最重要的是,Fabric.js的学习曲线非常平缓。其API设计得既简洁又强大,即便是初学者也能迅速上手并开发出功能丰富的图形应用程序。同时,社区中丰富的教程、示例和文档资源也为开发者提供了强大的支持。
总之,Fabric.js是每一个希望在网页上实现复杂图形交互的开发者不可或缺的工具。它以其强大的功能、直观的界面和易于上手的特点,让canvas操作变得前所未有的简单和高效。
2、实现多个页面切换
多个页面切换其实本质canvas始终都是一个,只是切换时根据数据进行更新,首先我们要将数据进行存储,为了方便操作可以使用context、redux、mobx都可以,这里本文使用的是mobx。
Mobx是一个功能强大的状态管理库,其特点主要包括:
- 响应式编程:通过透明的响应式编程方式,当应用状态发生变化时,自动更新依赖的视图或计算值,减少手动更新和优化的需要。
- 简单易用:API设计简洁,学习成本低,开发者可以快速上手并用于状态管理,提高开发效率。
- 高效性能:只重新执行必要的计算,避免不必要的渲染,提升应用性能。
- 可扩展性:支持多存储(store)和复杂的状态逻辑,适用于大型和复杂的应用场景。
- 灵活性:与各种前端框架(如React、Vue等)和后端框架(如Node.js)兼容,提供灵活的集成选项。
import { makeAutoObservable } from 'mobx'import { v4 as uuid } from 'uuid'export class PageItemStore { id = uuid() pageAngle = 0 showAllFilter = false filterKey = 'normal' filterStyle = {} rectColor = { type: 'bg', color: '#fff' } opacity = 0 canvasData = null constructor () { makeAutoObservable(this) }}
import { makeAutoObservable } from 'mobx'class CreateStore { canvas = null pageList = [new PageItemStore()] }
数据存储主要就分为这两块,将数据进行统一管理,对pageList进行操作(增删改查),因为canvas只有一个,所以在切换页面时我们需要获取到当前选中的页面获取它的canvas数据,然后使用loadFromJSON去进行加载
class CreateStore { changePage = (index, callback) => { this.pageIndex = index const pageItem = this.getCurrentPage() this.loadFromJSON(pageItem, callback) } addPage = () => { } deletePage = () => { } copyPage = () => { } loadFromJSON = (data, callback) => { if (!data) return this.setRectFilter({ style: data.filterStyle }) this.canvas.discardActiveObject() if (!data.canvasData) { return callback && callback() } this.canvas.loadFromJSON(data.canvasData, () => { const objects = this.canvas.getObjects() objects.forEach(item => { this.animation.carryAnimations(item) }) callback && callback() }) }}
3、页面生成缩略图
既然有多个页面,那咱也得知道每个页面的样子呀,所以这边做了一个缩略图的效果,首先我们使用存储的画布数据生成一个等比缩放n倍的小型canvas,然后监听画布数据的变化,重新渲染canvas
注意:一定要等比缩放,要不然会出现展示不全的现象
class PageStore { init = (canvas, workspace) => { this.canvas = canvas this.workspace = workspace this.render() } render = () => { this.modifiedCanvas() this.canvas.on('object:modified', this.modifiedCanvas) } modifiedCanvas = () => { const pageItem = this.getCurrentPage() pageItem.canvasData = this.workspace.toObject() }}
const ThumbCanvas = observer(({ data }) => { const container = useRef() const canvas = useRef() useEffect(() => { canvas.current = new fabric.StaticCanvas(container.current, { width: container.current.clientWidth * 2, height: container.current.clientHeight * 2 }) }, []) useEffect(() => { loadFromJSON() }, [data.rectColor, data.canvasData]) const loadFromJSON = () => { if (!canvas.current) return canvas.current.loadFromJSON(data.canvasData, () => { const rect = canvas.current.getObjects().find(item => item.id === WorkspaceId) const width = container.current.clientWidth const height = container.current.clientHeight const thumbZoom = width / 375 canvas.current.setZoom(thumbZoom) const thumbViewportTransform = canvas.current.viewportTransform thumbViewportTransform[4] = -rect.left * thumbZoom thumbViewportTransform[5] = -rect.top * thumbZoom canvas.current.setViewportTransform(thumbViewportTransform) canvas.current.renderAll() }) } return <canvas style={{ transform: 'scale(0.5)', transformOrigin: 'left top' }} ref={container}/>})
4、元素生成缩略图
首先获取到当前页面的全部元素,然后将它们转化成对象,之后调用toDataURL方法就能获取到图片了,同样为了保证图片的清晰图,也需要去等比放大,这样会显示的清楚一些
useEffect(() => { if (!page.canvasData || !page.canvasData.objects) return const list = page.canvasData.objects.filter(item => { return item.id !== WorkspaceId && item.id !== HoverBorderId }).reverse() setList(list)}, [page.canvasData])
const ObjectThumb = ({ object }) => { const [image, setImage] = useState('') const [isVideo, setIsVideo] = useState(false) useEffect(() => { if (object.videoUrl) { return setIsVideo(true) } try { const name = object.type[0].toUpperCase() + object.type.slice(1) fabric[name].fromObject(object, object => { object.set({ visible: true, opacity: 1 }) setImage(object.toDataURL()) }) } catch (err) { console.log(err) } }, [object]) if (isVideo) { return '视频' } if (!image) return null return <img style={{ zoom: '0.5' }} src={image} alt=""/>}
5、设置页面滤镜
这个设置滤镜我这里是纯靠css样式去设置的,到了H5里面展示也是一样的,整体页面增加滤镜,我不太清楚Fabric.js有没有这样的能力,因为我在调研时发现设置滤镜都是单独给图片元素去设置的,有懂的大佬可以指点一下,感谢!
css 设置滤镜developer.mozilla.org/zh-CN/searc…
export const filterList = [ { title: '原图', style: { filter: 'none' }, type: 'normal' }, { title: '胶片', style: { filter: 'brightness(112%) contrast(77%) saturate(150%) sepia(18%)' }, type: 'jiaopian' }, { title: '蓝调', style: { filter: 'contrast(75%) saturate(105%) hue-rotate(-35deg) sepia(18%) brightness(105%) grayscale(30%)' }, type: 'landiao' }, { title: '日系', style: { filter: 'contrast(99%) hue-rotate(-33deg) sepia(21%) brightness(91%)' }, type: 'rixi' }, { title: '午茶', style: { filter: 'hue-rotate(-11deg) saturate(226%) brightness(90%) contrast(120%) sepia(60%)' }, type: 'wucha' }, { title: '褪色', style: { filter: 'brightness(115%) contrast(80%) saturate(60%)' }, type: 'tuise' }, ]
6、给元素添加一些动画
Fabric.js还支持动画效果,我们可以去设置元素的属性,控制动画的时长,监听动画执行中,和监听动画结束的状态,这些都是支持的
我们需要将动画属性绑定在元素上,因为考虑到会有复制元素的场景,所以将这些属性绑定在元素中,复制的时候就一并给复制了,不需要我们去做一些特殊处理,所以我们新建元素的时候会默认给元素添加一个animateList,用于存放这个元素的所有动画
import { v4 as uuid } from 'uuid'const DefaultValue = { time: 1, delay: 0, count: 1, loop: false }export const animateType = { fadeIn: { name: '淡入', animateName: 'fadeIn', easing: 'easeInQuad', image: 'https://ossprod.jrdaimao.com/file/1723013159219136.svg', selectImage: 'https://ossprod.jrdaimao.com/file/1723012794337348.svg' }, leftToRight: { name: '向右移入', animateName: 'leftToRight', easing: 'easeOutQuad', image: 'https://ossprod.jrdaimao.com/file/1723013249717692.svg', selectImage: 'https://ossprod.jrdaimao.com/file/17230128203441.svg' }, rightToLeft: { name: '向左移入', animateName: 'rightToLeft', easing: 'easeOutQuad', image: 'https://ossprod.jrdaimao.com/file/1723013394770325.svg', selectImage: 'https://ossprod.jrdaimao.com/file/1723013368851234.svg' }, topToBottom: { name: '向下移入', animateName: 'topToBottom', easing: 'easeOutQuad', image: 'https://ossprod.jrdaimao.com/file/1723013447843661.svg', selectImage: 'https://ossprod.jrdaimao.com/file/1723013466318949.svg' }, }export const createAnimate = (type) => { const options = animateType[type] const name = options.name const animateName = options.animateName const easing = options.easing return { ...DefaultValue, name, animateName, easing, id: uuid() }}export const list = Object.keys(animateType).reduce((prev, next) => { return [ ...prev, { key: next, ...animateType[next] } ]}, [])
addText = (props) => { const { text: textValue = '双击编辑正文', fontSize = 14, ...otherProps } = props || {} const text = new fabric.Textbox(textValue, { fontSize, fontFamily: 'serif', fontWeight: 'normal', textAlign: 'justify-center', ...otherProps, ...this.createShareAttr() }) this.firstAddObject(text)}firstAddObject = (object) => { this.canvas.add(object).setActiveObject(object) this.workspace.align.center() object.animateList.push(createAnimate('fadeIn')) this.workspace.animation.carryAnimations() this.canvas.renderAll()}carryAnimations = (animateObject, value, callback) => { const object = animateObject || this.canvas.getActiveObject() let list = null if (value) { list = Array.isArray(value) ? value : [value] } else { list = object.animateList } if (!list || !list.length || !object) return object.set({ hasControls: false, borderColor: 'transparent' }) const newList = [...list] const start = (item) => { if (!object || !item) return if (item.delay) { this.sleep(this.toDuration(item.delay)) } const fn = this[item.animateName] fn && fn(object, item.time, item.easing).then(() => { if (newList.length === 0) { object.set({ hasControls: true, borderColor: 'blue' }) this.canvas.renderAll() callback && callback() } start(newList.shift()) }) } start(newList.shift())}
单个动画
多个动画
动画效果基本上都是大同小异,只要你知道怎么去设置元素的属性,和控制动画执行的时机就可以啦,像我这里是封装好了一整套的动画逻辑,可以随时去新加一些动画,而且不用改很多的代码哦,基本就是修改配置文件就好了
7、给元素添加点击事件
添加点击事件的道理总的来说其实和添加动画是一样的,也需要把事件绑定在元素中,在pc端这里我们就是需要记录一下点击事件的类型,真正处理事件的地方还是在H5里
8、最终效果
下面的例子中我们创建了几个页面并添加了一些动画效果和触发事件,总体来说效果还是可以的,如果能做出一些精美的模板把他们组合起来我想效果会更好一些
作者:洞窝-永升