• fabric 类组合容器


    想实现一个类似组合的容器,不过比组合使用起来方便一些,直接拖拽放入容器,可以整体移动 ,内部元素又可拖动在容器内移动

    fabric 目前没有这种功能,只能手写

    增加容器size 判断,更新关联,重叠层级判断等

    缺:嵌套,反序列化维持关联等

    代码留念:

    import { fabric } from 'fabric';
    import { KeyCode } from "./key-code";
    
    /**
     * 组容器 试验
     * 自由组内拖动或拖出
     * 元素拖入进组,更新层级关系
     * 组容器改变大小,重新处理关联,要注意组的放大比例转换为实际宽高
     * 层叠,元素拖拽进组时,关联唯一组
     * 组容器 为激活对象时(鼠标点击触发),设置透明度,方便观察内部元素。取消激活时(取消之前触发),还原为不透明,方便观察层级
     *
     * 未实现:
     * 嵌套的元素归属等
     *
     * 后续待处理
     * 包括序列化,反序列化数据关系维护
     */
    export class SimulationGroupBox {
        // 组与元素关联数组
        groupRelation = [];
        TYPE_GROUP = 'group';
        // 是否 group 修改宽高
        isGroupModified = false;
        // 是否组移动
        isGroupMoving = false;
        // 记录 mouse:down 事件,标记 group 起始位置
        groupStartPos = {};
    
        constructor(canvas) {
            // 重置长宽:
            canvas.setWidth(1000);
            canvas.setHeight(800);
            // 绘制图形
            // let groupBox = new fabric.Polyline(SimulationGroupBox.getPoints(), SimulationGroupBox.defaultLineBox());
            // 添加 rect
            this.addRect(canvas);
            // 绘制组容器
            this.addGroupBox(canvas, this.groupRelation);
            // 绑定画布监听
            this.bindCanvasEvents(canvas);
            // 绑定键盘
            this.bindKeyBoard(canvas);
        }
    
        /**
         * 绑定键盘事件
         * @param canvas
         */
        bindKeyBoard(canvas) {
            $(document).on('keydown', (e) => {
                const key = e.originalEvent.keyCode;
                let objs = null;
                switch (key) {
                    case KeyCode.Q: // 打印显示
                        console.log(canvas.getObjects());
                        break;
                    case KeyCode.R: // 添加 rect
                        this.addRect(canvas);
                        break;
                    case KeyCode.G: // 添加 组容器
                        this.addGroupBox(canvas, this.groupRelation);
                        break;
                    case KeyCode.B: // 更新 组容器 样式属性
                        objs = canvas.getActiveObjects();
                        if (objs.length === 1 && objs[0].id.match(new RegExp(this.TYPE_GROUP))) {
                            let group = objs[0];
                            let r = Math.round(Math.random() * 20);
                            let sw = Math.round(Math.random() * 10);
                            group.set({
                                rx: r,
                                ry: r,
                                stroke: SimulationGroupBox.getColor(),
                                strokeWidth: sw,
                            });
                            canvas.renderAll();
                        }
                        break;
                    case KeyCode.T: // 上一层
                        objs = canvas.getActiveObjects();
                        if (objs.length === 1) {
                            objs[0].bringForward();
                            // img.sendBackwards(); // 向下跳一层
                            // img.sendToBack(); // 向下跳底层
                            // img.bringForward(); // 向上跳一层
                            // img.bringToFront(); // 向上跳顶层
                        }
                        break;
                    case KeyCode.Y: // 下一层
                        objs = canvas.getActiveObjects();
                        if (objs.length === 1) {
                            objs[0].sendBackwards();
                        }
                        break;
                }
            });
        }
    
        /**
         * 添加 测试矩形
         * @param canvas
         */
        addRect(canvas) {
            let rect = new fabric.Rect(SimulationGroupBox.defaultRect());
            canvas.add(rect);
        }
    
        /**
         * 添加 组容器
         * @param canvas
         * @param groupRelation
         */
        addGroupBox(canvas, groupRelation) {
            let groupBox = new fabric.Rect(SimulationGroupBox.defaultGroup());
            // 创建组后,维护关系数组
            groupRelation.push({
                groupId: groupBox.id,
                group: groupBox,
                members: [],
            });
            canvas.add(groupBox);
        }
    
        /**
         * 画布绑定事件
         * @param canvas
         */
        bindCanvasEvents(canvas) {
            // 标记非组元素移动
            let isMoving = false;
            canvas.on('before:selection:cleared', (e) => {
                if (e.target && e.target.id && e.target.id.match(new RegExp(this.TYPE_GROUP))) {
                    // 还原组容器透明度,方便观察层级
                    e.target.set('opacity', 1);
                }
            });
            canvas.on('mouse:down', (e) => {
                if (e.target && e.target.id && e.target.id.match(new RegExp(this.TYPE_GROUP))) {
                    // 设置组容器透明度,方便显示内部元素
                    e.target.set('opacity', 0.5);
                }
            });
            canvas.on('mouse:move', (e) => {
                if (e.target && e.target.id && e.target.id.match(new RegExp(this.TYPE_GROUP))) {
                    // 标记 group 和关联元素的起始位置
                    this.groupStartPos = this.getMouseStartPos(e.target, this.groupRelation);
                }
            });
            // canvas.on('mouse:move', (e) => {
            //     // 可重复绑定多次事件
            //     console.log(1);
            // });
            canvas.on('object:moving', (e) => {
                if (e.target && e.target.id && e.target.id.match(new RegExp(this.TYPE_GROUP))) {
                    // 标记 group 移动
                    !this.isGroupMoving && (this.isGroupMoving = true);
                    this.moveGroupBox(e.target, this.groupStartPos, this.groupRelation);
                } else if (e.target) {
                    // 标记非 组容器 元素移动
                    isMoving = true;
                }
            });
            canvas.on('object:scaling', (e) => {
                if (e.target && e.target.id && e.target.id.match(new RegExp(this.TYPE_GROUP))) {
                    // 标记 group 宽高改变
                    !this.isGroupModified && (this.isGroupModified = true);
                }
            });
            canvas.on('object:modified', (e) => {
                if ( this.isGroupModified && e.target && e.target.id && e.target.id.match(new RegExp(this.TYPE_GROUP))) {
                    // 更新组信息,将缩放比例更新为实际宽高,后续更新关联元素时,必须使用实际位置信息判断!
                    e.target.set({
                         e.target.width * e.target.scaleX,
                        height: e.target.height * e.target.scaleY,
                        scaleX: 1,
                        scaleY: 1,
                    });
                    // 更新与元素关联
                    this.updateGroupRelation(canvas, e.target, this.groupRelation);
                }
            });
            canvas.on('mouse:up', (e) => {
                if (e.target && e.target.id && e.target.id.match(new RegExp(this.TYPE_GROUP))) {
                    canvas.renderAll();
                    // 重置相关标记
                    this.isGroupModified && (this.isGroupModified = false);
                    if (this.isGroupMoving) {
                        this.isGroupMoving = false;
                        // 清空组容器选中
                        canvas.discardActiveObject();
                        canvas.renderAll();
                        // 更新与元素关联
                        this.updateGroupRelation(canvas, e.target, this.groupRelation);
                    }
                    this.groupStartPos = {};
                }
                if (isMoving && e.target) {
                    // 移动其他元素,判断是否落在 组容器 中
                    this.createRelationWithGroup(e.target, this.groupRelation, canvas);
                    // 重置标记
                    isMoving = false;
                }
            });
        }
    
        /**
         * 获取鼠标在 组容器 down 时,组容器及内部元素,起始位置
         * @param target
         * @param groupRelation
         * @returns {{top: *, left: *, members: []}}
         */
        getMouseStartPos(target, groupRelation) {
            let pos = {
                top: target.top,
                left: target.left,
                members: [],
            };
            // 获取组对应元素,设置移动位置
            let group = groupRelation.filter((item) => {
                if (item.groupId === target.id) {
                    return item;
                }
            })[0];
            group.members.forEach((m) => {
                pos.members.push({
                    top: m.top,
                    left: m.left,
                });
            });
            return pos;
        }
    
        /**
         * 移动元素后,判断元素位置,创建与 组容器 关联,注意 容器组层级,及 关联唯一 容器组
         * @param ele
         * @param groupRelation
         * @param canvas
         */
        createRelationWithGroup(ele, groupRelation, canvas) {
            // 保存可创建关联的组
            let canRelationIds = [];
            // 判断元素是否在 group 内部
            groupRelation.forEach((item) => {
                let g = item.group;
                // 是否存在对应关联
                let sub;
                let i = item.members.filter((m, index) => {
                    if (m.id === ele.id) {
                        sub = index;
                        return m;
                    }
                });
                // 移除关联性
                if (i.length) {
                    // 取消关联
                    item.members.splice(sub, 1);
                }
                // 暂时不考虑组的嵌套、重叠问题,判断有误,要限制区间
                if (g.top <= ele.top && (ele.top + ele.height <= g.top + g.height) &&
                    g.left < ele.left && (ele.left + ele.width <= g.left + g.width)) {
                    // 保存可关联组
                    canRelationIds.push(item.groupId);
                }
            });
            if (canRelationIds.length) {
                let item = this.getUpperGroup(canvas, groupRelation, canRelationIds);
                if (item) {
                    item.members.push(ele);
                    // 如果当前元素层级在组容器下,则更新层级到组之上
                    this.updateLevel(canvas, item.group, ele);
                }
            }
        }
    
        /**
         * 重叠情况下,获取更高层级的 组容器
         * @param canvas
         * @param groupRelation
         * @param canRelationIds
         * @returns {null|*}
         */
        getUpperGroup(canvas, groupRelation, canRelationIds) {
            let list = this.getIdIndexList(canvas);
            let max = -1;
            let res = '';
            canRelationIds.forEach((id) => {
                if (list[id] > max) {
                    max = list[id];
                    res = id;
                }
            });
            let item = groupRelation.filter((item) => {
                if (item.groupId === res) {
                    return item;
                }
            });
            if (item.length) {
                return item[0];
            }
            return null;
        }
    
        /**
         * 更新层级关系,一对一更新
         * @param canvas
         * @param group
         * @param target
         * @param list [id: index],可传入
         */
        updateLevel(canvas, group, target, list = []) {
            list.length === 0 && (list = this.getIdIndexList(canvas));
            let gIndex = list[group.id];
            let tIndex = list[target.id];
            if (gIndex > tIndex) {
                let num = gIndex - tIndex;
                while(num > 0) {
                    // 上移
                    target.bringForward();
                    num--;
                }
            }
        }
    
        /**
         * 获取键值对 [id: index]
         * @param canvas
         */
        getIdIndexList(canvas) {
            let list = {};
            canvas.getObjects().forEach((item, index) => {
                list[item.id] = index;
            });
            return list;
        }
    
        /**
         * 编辑 组容器 宽高后,更新元素关联
         * @param canvas
         * @param target 组容器
         * @param groupRelation
         */
        updateGroupRelation(canvas, target, groupRelation) {
            let group = groupRelation.filter((item) => {
                if (target.id === item.groupId) {
                    return item;
                }
            })[0];
            group.members = [];
            // 获取 id => index 键值对
            let list = this.getIdIndexList(canvas);
            // 要排除已编组成员
            let exclude = this.getExcludeIds(groupRelation, target.id);
            canvas.getObjects().forEach((item) => {
                // 非自己,非其他 组容器 成员
                if (item.id !== target.id && !exclude.includes(item.id) &&
                    target.top <= item.top && (item.top + item.height <= target.top + target.height) &&
                    target.left < item.left && (item.left + item.width <= target.left + target.width)) {
                    // 如果未关联,则关联
                        group.members.push(item);
                        // 如果当前元素层级在组容器下,则更新层级到组之上
                        this.updateLevel(canvas, target, item, list);
                }
            });
        }
    
        /**
         *
         * 根据关联,获取其他组成员信息
         * @param groupRelation
         * @param targetId
         * @returns [ids]
         */
        getExcludeIds(groupRelation, targetId) {
            let list = [];
            groupRelation.forEach((item) => {
                if (item.groupId !== targetId) {
                    let ids = item.members.map((m) => {
                        return m.id;
                    });
                    list = [...ids];
                }
            });
            return list;
        }
    
        /**
         * 移动 组容器 ,维护组内元素统一移动
         * @param target
         * @param groupStartPos
         * @param groupRelation
         */
        moveGroupBox(target, groupStartPos, groupRelation) {
            // 获取差值
            let moveLeft = target.left - groupStartPos.left;
            let moveTop = target.top - groupStartPos.top;
            // 获取组对应元素,设置移动位置
            let group = groupRelation.filter((item) => {
                if (item.groupId === target.id) {
                    return item;
                }
            })[0];
    
            let members = groupStartPos.members;
            group.members.forEach((m, sub) => {
                let left = members[sub]['left'] + moveLeft;
                let top = members[sub]['top'] + moveTop;
                m.set({
                    left: left,
                    top: top,
                });
                // 如果您希望更新事件触发区域,则还需要调用setCoords。
                m.setCoords();
            });
        }
    
        /**
         * rect 模拟组 样式
         * @returns {{top: number, left: number, rx: number, ry: number,  number, id: string, fill: string, stroke: string, height: number}}
         */
        static defaultGroup() {
            return {
                id: SimulationGroupBox.getId('group'),
                fill: '#eee',
                stroke: '#000',
                rx: 10,
                ry: 10,
                top: 100,
                left: 200,
                 300,
                height: 300,
                // opacity: 0.6, 移动时设置透明度
            };
        }
    
        /**
         * 简单矩形
         */
        static defaultRect() {
            return {
                id: SimulationGroupBox.getId('rect'),
                fill: SimulationGroupBox.getColor(),
                left: 50,
                top: 50,
                 100,
                height: 100,
            };
        }
    
        /**
         * 获取 id = type_time
         * @param type
         * @returns {string}
         */
        static getId(type) {
            return type + '_' + new Date().getTime();
        }
    
        /**
         * 获取随机颜色
         * @returns {string}
         */
        static getColor() {
            let arr = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' ];
            let a = Math.round(Math.random() * 15);
            let b = Math.round(Math.random() * 15);
            let c = Math.round(Math.random() * 15);
            let d = Math.round(Math.random() * 15);
            let e = Math.round(Math.random() * 15);
            let f = Math.round(Math.random() * 15);
            return '#' + arr[a] + arr[b] + arr[c] + arr[d] + arr[e] + arr[f];
        }
    
        /**
         * 组边框样式
         * @returns {{eleType: string, fill: string, stroke: string}}
         */
        static defaultLineBox() {
            return {
                eleType: 'group',
                fill: '#eee', // 透明
                stroke: '#000',
            }
        }
    
        /**
         * 容器组边框
         * @returns {[{x: number, y: number},{x: number, y: number},{x: number, y: number},{x: number, y: number},{x: number, y: number}]}
         */
        static getPoints() {
            // 保存这下所有点的起点和终点坐标
            return [
                { x: 300, y: 100 },
                { x: 600, y: 100 },
                { x: 600, y: 400 },
                { x: 300, y: 400 },
                { x: 300, y: 100 },
            ];
        }
    }
  • 相关阅读:
    Linux
    C/C++ 引入头文件时 #include<***.h> 与 #include"***.h" 区别
    2018.01.10
    java 选择结构if
    引用数据类型 Scanner Random
    java 运算符
    java变量和数据类型
    Java se基础
    数据库设计
    MySQL 其他基础知识
  • 原文地址:https://www.cnblogs.com/guofan/p/16203398.html
Copyright © 2020-2023  润新知