服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服务器之家 - 编程语言 - JavaScript - js教程 - fabric.js图层功能独立显隐 添加 删除 预览实现详解

fabric.js图层功能独立显隐 添加 删除 预览实现详解

2023-05-06 15:51圊妖 js教程

这篇文章主要为大家介绍了fabric.js图层功能独立显隐 添加 删除 预览实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

去年经历了个虚拟人的项目,其中我参与了前端的部分,一个用electron写的编辑器,UI部分用的vue3+tsx的写法(这种组合是不是没见过?我也是第一次见,惊得我当时还发了个沸点)

fabric.js图层功能独立显隐 添加 删除 预览实现详解

我所负责的部分是让用户可以对贴图进行修改,其中就涉及到了图层功能(类似Photoshop),而我当时选用的fabric是没有图层的,因此我就得考虑如何实现图层

原理

fabric本身有提供group功能,本意是让你将画布上的一些元素组合起来,这也将成为本次图层功能的基础 既以一个group代表一个图层,画布下第一层children只有图层(group),而在group中,才是用户实际绘制的内容

效果预览

本次demo实现:

  • 用户可手动添加/删除图层
  • 可对每个图层进行独立显隐操作,并反馈到画布中
  • 可对每个图层单独预览

效果图:

fabric.js图层功能独立显隐 添加 删除 预览实现详解

(别嫌我的样式丑,我已经下班了,没有UI能够PUSH我!)

下期预告

  • 让图层能够调整图层层级
  • 结合undo + redo + 橡皮擦

小Tips

首先fabric是需要到官方上下载的,在选择你需要的模块后再进行打包

虽然npm上也可以下载,但那不是官方的包,是有网友打包好以后上传的,其中没有包含橡皮擦模块,很可能会不符合你的需求

所以我个人建议你可以自行去官网上打包,然后传到你的私有npm库里,然后就可以通过npm来管理了

fabric自定义打包下载地址:Custom Fabric build — Fabric.js Javascript Canvas Library (fabricjs.com)

fabric的事件文档:Event inspector | Fabric.js Demos (fabricjs.com)

接下来的demo将会通过直接引入的方式来使用fabric,虽然我平时写项目都是ts,但练手demo我个人建议还是js,问就是省事

完整代码

目录:

  • fabric.js即为官网下载的插件包,这个文件就不放了,大家可以自行去官网打包下载
  • index.html即本次的页面,主要负责dom的处理,直接扔浏览器运行就可以
  • sketchpad.js 是对fabric的二次封装,同时也避免在html中写太多fabric功能代码

fabric.js图层功能独立显隐 添加 删除 预览实现详解

index.html代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .content {
            display: flex;
        }
        .preview {
            margin-top: 40px;
            padding-top: 20px;
            width: 100%;
            display: flex;
            justify-content: center;
            border-top: 1px solid rgba(0,0,0,0.1);
        }
        .preview > img {
            width: 300px;
            height: 200px;
        }
        .layer-list {
            width: 300px;
            display: flex;
            flex-direction: column;
            padding-right: 10px;
            margin-right: 10px;
            box-sizing: content-box;
            border-right: 1px solid rgba(0,0,0, 0.2);
        }
        .layer {
            width: 300px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 5px;
            border: 1px solid rgba(0,0,0, 0.2);
        }
        .layer > img {
            width: 30px;
            height: 30px;
            border: 1px solid rgba(0,0,0,0.1);
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="content">
            <!-- 左侧的图层列表 -->
            <div class="layer-list">
                <button style="margin-bottom: 20px;" @click="addLayer">增加图层</button>
                <div @click="changeCurrentLayer(item.id)" class="layer" :style="currentLayer === item.id ? 'background-color: rgba(0,0,0, 0.1)' : '' " v-for="item of layers" :key="item.id">
                    <button @click="changeVisible(item.id)">{{ item.show ? '已显示' : '已隐藏'}}</button>
                    <img :src="item.data">
                    <span>{{ item.name }}</span>
                    <button @click="deleteLayer(item.id)">删除</button>
                </div>
            </div>
            <!-- 右侧的画板 -->
            <div class="sketchpad-layout"  style="width: 600px;">
                <canvas id="sketchpad" width="600" height="400" style="border: 1px solid #ccc;"></canvas>
            </div>
        </div>
        <!-- 对整张画布进行图片预览 -->
        <div class="preview">
            <button @click="updatePreview">整个画布预览:</button>
            <img :src="preview">
        </div>
    </div>
    <!-- 使用vue3来进行ui的渲染,懒得操作dom了 -->
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <!-- 打包好的fabric文件 -->
    <script src="./fabric.js"></script>
    <!-- sketchpad里面就是将fabric封装了一层 -->
    <script src="./sketchpad.js"></script>
    <script>
        const { createApp } = Vue
        /** 单条的数据类型定义 */
        const LayerData = {
            /** 用于显示的名称 */
            name: '图层名称',
            /** 图层的id,也用于管理图层 */
            id: '1111',
            /** 图层的显示状态 */
            show: true,
            /** 图层的数据,用于显示预览图 */
            data: '',
        }
        createApp({
            data() {
                return {
                    layers: [],// 图层数组,方便管理
                    sketchpad: null,// 画板
                    currentLayer: '',// 当前图层的id
                    preview: '',// 预览图的base64数据
                }
            },
            methods: {
                /**
                 * 改变图层的显示/隐藏
                 * @param id 图层的id
                */
                changeVisible(id) {
                    const index = this.layers.findIndex(v => v.id === id);
                    if (index > -1) {
                        this.layers[index].show = !this.layers[index].show;
                    }
                    this.sketchpad.changeLayerVisible(id)
                },
                /**
                 * 删除图层
                 * @param id 图层的id
                */
                deleteLayer(id) {
                    const index = this.layers.findIndex(v => v.id === id);
                    if (index > -1) {
                        this.layers.splice(index, 1)
                        this.sketchpad.deleteLayer(id)
                    }
                },
                /**
                 * 增加图层
                */
                addLayer() {
                    const item = {
                        ...LayerData
                    }
                    item.id = new Date().getTime()
                    item.name = `图层${this.layers.length + 1}`
                    this.layers.push(item)
                    this.sketchpad.addLayer(item.id)
                    this.changeCurrentLayer(item.id)
                },
                /** 选择当前要操作的图层 */
                changeCurrentLayer(id) {
                    this.currentLayer = id
                    this.sketchpad.changeCurrentLayer(id)
                },
                /**
                 * 更新预览图
                */
                updatePreview() {
                    this.preview = this.sketchpad.getImage()
                },
                /** 图层数据更新的回调 */
                onChangeData(id, data) {
                    const index = this.layers.findIndex(v => v.id === id);
                    if (index > -1) {
                        this.layers[index].data = data;
                    }
                }
            },
            mounted() {
                this.sketchpad = new Sketchpad('sketchpad', {
                    change: this.onChangeData
                });
                this.addLayer()
            }
        }).mount('#app')
    </script>
</body>
</html>

sketchpad.js代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
console.log('Sketchpad load');
class Sketchpad {
    /** fabric实例 */
    instance = null;
    /** 当前所在图层的id */
    currentLayer = '';
    /** 画布宽度 */
    width = 600;
    /** 画布高度 */
    height = 600
    /** 事件订阅 */
    listeners = {
        /**
         * 图层内容变化时的回调
         * @param {string} id 图层id
         * @param {base64} data 图层内容的base64格式
         */
        change: (id, data) => {}
    }
    constructor(id, listeners) {
        this.instance = new fabric.Canvas(id);
        this.width = this.instance.width;
        this.height = this.instance.height;
        this.instance.isDrawingMode = true;
        this.listeners.change = listeners.change
        this.instance.on('object:added', ((options) => {
                if (options.target.type === 'group') return;
                const groups = this.instance.getObjects()
                groups.forEach(v => {
                    if (v.layerId === this.currentLayer  && v.type === 'group') {
                        v.addWithUpdate(options.target);
                        this.instance.remove(options.target);
                        this.listeners.change(v.layerId, v.toDataURL({
                            width: this.width,
                            height: this.height
                        }))
                    }
                })
        }))
        console.log('Sketchpad init')
    }
    /** 添加图层 */
    addLayer(id) {
        const group = new fabric.Group([], {
            width: this.width,
            height: this.width,
        });
        // 在这里增加一个自定义属性 layerId ,用于区分图层
        group.layerId = id
        this.instance.add(group)
        this.currentLayer = id;
            this.listeners.change(id, group.toDataURL({
                width: this.width,
                height: this.height
            }))
    }
    /** 改变图层的显示/隐藏 */
    changeLayerVisible(id) {
        const groups = this.instance.getObjects()
        groups.forEach(v => {
            if (v.layerId === id && v.type === 'group') {
                v.visible = !v.visible;
                this.instance.renderAll() // 刷新画布,改变group的visible属性,必须通过刷新画布,才能应用新属性值
            }
        })
    }
    /** 选择要操作的图层 */
    changeCurrentLayer(id) {
        this.currentLayer = id
    }
    /** 删除图层 */
    deleteLayer(id) {
        const groups = this.instance.getObjects()
        groups.forEach(v => {
            if (v.layerId === id && v.type === 'group') {
                this.instance.remove(v)
                this.instance.renderAll() // 刷新画布
            }
        })
    }
    /** 获取画布数据,以img标签可以识别的base64格式 */
    getImage() {
        return this.instance.toDataURL()
    }
}

将以上这两个文件代码直接复制粘贴到编辑器里,然后再去打包个fabric.js也放进编辑器里,就可以运行啦

以上就是fabric.js图层功能独立显隐 添加 删除 预览实现详解的详细内容,更多关于fabric.js图层功能实现的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/7228980394014785593

延伸 · 阅读

精彩推荐
  • js教程详解CocosCreator MVC架构

    详解CocosCreator MVC架构

    这篇文章主要介绍了CocosCreator MVC架构,同学们在制作游戏过程中,尽量使用一些架构,会避免很多问题...

    houjia15911152022-03-03
  • js教程五个必须知道的 JavaScript 数组方法

    五个必须知道的 JavaScript 数组方法

    数组非常适合存储相关数据,并且通常用作组织信息的一种方式。 我们中的大多数人每天都在使用它们,但是您知道 JavaScript 中还内置了一些非常简洁的数...

    七爪网4942022-10-18
  • js教程用纯JS实现二级菜单效果

    用纯JS实现二级菜单效果

    这篇文章主要为大家详细介绍了用纯JS实现二级菜单效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    贪吃ღ大魔王6362022-03-09
  • js教程如何用JS实现网页瀑布流布局

    如何用JS实现网页瀑布流布局

    这篇文章主要介绍了如何用JS实现网页瀑布流布局,帮助大家更好的利用JavaScript制作网页,感兴趣的朋友可以了解下...

    范佐11822022-03-09
  • js教程js面向对象封装级联下拉菜单列表的实现步骤

    js面向对象封装级联下拉菜单列表的实现步骤

    这篇文章主要介绍了js面向对象封装级联下拉菜单列表的实现步骤,帮助大家更好的理解和使用JavaScript,感兴趣的朋友可以了解下...

    蒋伟平8952022-01-19
  • js教程利用javaScript处理常用事件详解

    利用javaScript处理常用事件详解

    这篇文章主要介绍了利用javaScript处理常用事件详解,文章有非常详细的代码实践,对学习js的小伙伴们有一定的参考价值,需要的朋友可以参考下...

    花狗Fdog3762022-03-01
  • js教程JS实现纸牌发牌动画

    JS实现纸牌发牌动画

    这篇文章主要为大家详细介绍了JS实现纸牌发牌动画,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    计算机的皇帝5612022-01-04
  • js教程JavaScript canvas实现雨滴特效

    JavaScript canvas实现雨滴特效

    这篇文章主要为大家详细介绍了JavaScript canvas实现雨滴特效,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    huangdong19317282021-12-29