wxa-comp-canvas-drag icon indicating copy to clipboard operation
wxa-comp-canvas-drag copied to clipboard

能否增加置于底层/置于顶层功能

Open ymli1981 opened this issue 6 years ago • 10 comments

多个图片重叠,希望能增加置于底层/顶层功能,方便选择拖动

ymli1981 avatar Nov 19 '18 22:11 ymli1981

你是说一键底层/顶层的功能吗

jasondu avatar Nov 20 '18 06:11 jasondu

对,可以在图片一个角上加个图标,点一下图标,就置顶/置底了

ymli1981 avatar Nov 20 '18 07:11 ymli1981

好的,我晚点提供这个api出来

jasondu avatar Nov 20 '18 07:11 jasondu

还有个问题,能否设置图形选中后,点x才删除? 有时操作某个图形时,会误删其他的图形(没选中它,x不显示,但只要点了它的x所在位置,就会被删除)

ymli1981 avatar Nov 21 '18 05:11 ymli1981

误删问题,自己解决了

ymli1981 avatar Nov 21 '18 05:11 ymli1981

还有个问题,能否设置图形选中后,点x才删除? 有时操作某个图形时,会误删其他的图形(没选中它,x不显示,但只要点了它的x所在位置,就会被删除)

新版本已经解决了误删问题。

PlayerYK avatar Feb 22 '19 02:02 PlayerYK

多个图层的时候能不能让选中的图层置顶

ming645463631 avatar Apr 24 '19 02:04 ming645463631

点图置顶功能 把index.js内容替换为以下内容 // components/canvas-drag/index.js const DELETE_ICON = './icon/close.png'; // 删除按钮 const DRAG_ICON = './icon/scale.png'; // 缩放按钮 const STROKE_COLOR = 'red'; const ROTATE_ENABLED = true;

const DEBUG_MODE = false; // 打开调试后会渲染操作区域边框(无背景时有效) const dragGraph = function ({x = 30, y = 30, w, h, type, text, fontSize = 20, color = 'red', url = null, rotate = 0, sourceId = null, selected = true,alpha = 1}, canvas, factor) { if (type === 'text') { canvas.setFontSize(fontSize); const textWidth = canvas.measureText(text).width; const textHeight = fontSize + 10; this.centerX = x + textWidth / 2; this.centerY = y + textHeight / 2; this.w = textWidth; this.h = textHeight; } else { this.centerX = x + w / 2; this.centerY = y + h / 2; this.w = w; this.h = h; }

this.x = x;
this.y = y;

// 4个顶点坐标
this.square = [
    [this.x, this.y],
    [this.x + this.w, this.y],
    [this.x + this.w, this.y + this.h],
    [this.x, this.y + this.h]
];

this.fileUrl = url;
this.text = text;
this.fontSize = fontSize;
this.color = color;
this.ctx = canvas;
this.rotate = rotate;
this.type = type;
this.selected = selected;
this.factor = factor;
this.alpha = alpha;
this.sourceId = sourceId;
this.MIN_WIDTH = 20;
this.MIN_FONTSIZE = 10;

};

//新加代码****** //数组交换位置,用于点击切换层 const change_pos = function (arr, k, j) { var c = arr[k]; arr[k] = arr[j]; arr[j] = c; return arr }; //新加代码结束****

dragGraph.prototype = { /** * 绘制元素 */ paint() { this.ctx.save(); // 由于measureText获取文字宽度依赖于样式,所以如果是文字元素需要先设置样式 let textWidth = 0; let textHeight = 0; if (this.type === 'text') { this.ctx.setFontSize(this.fontSize); this.ctx.setTextBaseline('middle'); this.ctx.setTextAlign('center'); this.ctx.setFillStyle(this.color); textWidth = this.ctx.measureText(this.text).width; textHeight = this.fontSize + 10; // 字体区域中心点不变,左上角位移 this.x = this.centerX - textWidth / 2; this.y = this.centerY - textHeight / 2; }

    // 旋转元素
    this.ctx.translate(this.centerX, this.centerY);
    this.ctx.rotate(this.rotate * Math.PI / 180);
    this.ctx.translate(-this.centerX, -this.centerY);
    // 渲染元素
    if (this.type === 'text') {
        this.ctx.fillText(this.text, this.centerX, this.centerY);
    } else if (this.type === 'image') {
		this.ctx.setGlobalAlpha(this.alpha)
        this.ctx.drawImage(this.fileUrl, this.x, this.y, this.w, this.h);
    }
    // 如果是选中状态,绘制选择虚线框,和缩放图标、删除图标
    if (this.selected) {
        this.ctx.setLineDash([2, 5]);
        this.ctx.setLineWidth(2);
        this.ctx.setStrokeStyle(STROKE_COLOR);
        this.ctx.lineDashOffset = 6;

        if (this.type === 'text') {
            this.ctx.strokeRect(this.x, this.y, textWidth, textHeight);
            this.ctx.drawImage(DELETE_ICON, this.x - 15, this.y - 15, 30, 30);
            this.ctx.drawImage(DRAG_ICON, this.x + textWidth - 15, this.y + textHeight - 15, 30, 30);
        } else {
            this.ctx.strokeRect(this.x, this.y, this.w, this.h);
            this.ctx.drawImage(DELETE_ICON, this.x - 15, this.y - 15, 30, 30);
            this.ctx.drawImage(DRAG_ICON, this.x + this.w - 15, this.y + this.h - 15, 30, 30);
        }
    }
    this.ctx.restore();
},
/**
 * 给矩形描边
 * @private
 */
_drawBorder() {
    let p = this.square;
    let ctx = this.ctx;
    this.ctx.save();
    this.ctx.beginPath();
    ctx.setStrokeStyle('orange');
    this._draw_line(this.ctx, p[0], p[1]);
    this._draw_line(this.ctx, p[1], p[2]);
    this._draw_line(this.ctx, p[2], p[3]);
    this._draw_line(this.ctx, p[3], p[0]);
    ctx.restore();
},
/**
 * 画一条线
 * @param ctx
 * @param a
 * @param b
 * @private
 */
_draw_line(ctx, a, b) {
    ctx.moveTo(a[0], a[1]);
    ctx.lineTo(b[0], b[1]);
    ctx.stroke();
},
/**
 * 判断点击的坐标落在哪个区域
 * @param {*} x 点击的坐标
 * @param {*} y 点击的坐标
 */
isInGraph(x, y) {
    // 删除区域左上角的坐标和区域的高度宽度
    const delW = 30;
    const delH = 30;

    // 旋转后的删除区域坐标
    const transformedDelCenter = this._rotatePoint(this.x, this.y, this.centerX, this.centerY, this.rotate);
    const transformDelX = transformedDelCenter[0] - delW / 2;
    const transformDelY = transformedDelCenter[1] - delH / 2;

    // 变换区域左上角的坐标和区域的高度宽度
    const scaleW = 30;
    const scaleH = 30;
    const transformedScaleCenter = this._rotatePoint(this.x + this.w, this.y + this.h, this.centerX, this.centerY, this.rotate);

    // 旋转后的变换区域坐标
    const transformScaleX = transformedScaleCenter[0] - scaleW / 2;
    const transformScaleY = transformedScaleCenter[1] - scaleH / 2;

    // 调试使用,标识可操作区域
    if (DEBUG_MODE) {
        // 标识删除按钮区域
        this.ctx.setLineWidth(1);
        this.ctx.setStrokeStyle('red');
        this.ctx.strokeRect(transformDelX, transformDelY, delW, delH);
        // 标识旋转/缩放按钮区域
        this.ctx.setLineWidth(1);
        this.ctx.setStrokeStyle('black');
        this.ctx.strokeRect(transformScaleX, transformScaleY, scaleW, scaleH);
        // 标识移动区域
        this._drawBorder();
    }

    if (x - transformScaleX >= 0 && y - transformScaleY >= 0 &&
        transformScaleX + scaleW - x >= 0 && transformScaleY + scaleH - y >= 0) {
        // 缩放区域
        return 'transform';
    } else if (x - transformDelX >= 0 && y - transformDelY >= 0 &&
        transformDelX + delW - x >= 0 && transformDelY + delH - y >= 0) {
        // 删除区域
        return 'del';
    } else if (this.insidePolygon(this.square, [x, y])) {
        return 'move';
    }
    // 不在选择区域里面
    return false;
},
/**
 *  判断一个点是否在多边形内部
 *  @param points 多边形坐标集合
 *  @param testPoint 测试点坐标
 *  返回true为真,false为假
 *  */
insidePolygon(points, testPoint) {
    let x = testPoint[0], y = testPoint[1];
    let inside = false;
    for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
        let xi = points[i][0], yi = points[i][1];
        let xj = points[j][0], yj = points[j][1];

        let intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
        if (intersect) inside = !inside;
    }
    return inside;
},
/**
 * 计算旋转后矩形四个顶点的坐标(相对于画布)
 * @private
 */
_rotateSquare() {
    this.square = [
        this._rotatePoint(this.x, this.y, this.centerX, this.centerY, this.rotate),
        this._rotatePoint(this.x + this.w, this.y, this.centerX, this.centerY, this.rotate),
        this._rotatePoint(this.x + this.w, this.y + this.h, this.centerX, this.centerY, this.rotate),
        this._rotatePoint(this.x, this.y + this.h, this.centerX, this.centerY, this.rotate),
    ];
},
/**
 * 计算旋转后的新坐标(相对于画布)
 * @param x
 * @param y
 * @param centerX
 * @param centerY
 * @param degrees
 * @returns {*[]}
 * @private
 */
_rotatePoint(x, y, centerX, centerY, degrees) {
    let newX = (x - centerX) * Math.cos(degrees * Math.PI / 180) - (y - centerY) * Math.sin(degrees * Math.PI / 180) + centerX;
    let newY = (x - centerX) * Math.sin(degrees * Math.PI / 180) + (y - centerY) * Math.cos(degrees * Math.PI / 180) + centerY;
    return [newX, newY];
},
/**
 *
 * @param {*} px 手指按下去的坐标
 * @param {*} py 手指按下去的坐标
 * @param {*} x 手指移动到的坐标
 * @param {*} y 手指移动到的坐标
 * @param {*} currentGraph 当前图层的信息
 */
transform(px, py, x, y, currentGraph) {
    // 获取选择区域的宽度高度
    if (this.type === 'text') {
        this.ctx.setFontSize(this.fontSize);
        const textWidth = this.ctx.measureText(this.text).width;
        const textHeight = this.fontSize + 10;
        this.w = textWidth;
        this.h = textHeight;
        // 字体区域中心点不变,左上角位移
        this.x = this.centerX - textWidth / 2;
        this.y = this.centerY - textHeight / 2;
    } else {
        this.centerX = this.x + this.w / 2;
        this.centerY = this.y + this.h / 2;
    }

    const diffXBefore = px - this.centerX;
    const diffYBefore = py - this.centerY;
    const diffXAfter = x - this.centerX;
    const diffYAfter = y - this.centerY;

    const angleBefore = Math.atan2(diffYBefore, diffXBefore) / Math.PI * 180;
    const angleAfter = Math.atan2(diffYAfter, diffXAfter) / Math.PI * 180;

    // 旋转的角度
    if (ROTATE_ENABLED) {
        this.rotate = currentGraph.rotate + angleAfter - angleBefore;
    }

    const lineA = Math.sqrt(Math.pow((this.centerX - px), 2) + Math.pow((this.centerY - py), 2));
    const lineB = Math.sqrt(Math.pow((this.centerX - x), 2) + Math.pow((this.centerY - y), 2));
    if (this.type === 'image') {
        let resize_rito = lineB / lineA;
        let new_w = currentGraph.w * resize_rito;
        let new_h = currentGraph.h * resize_rito;

        if (currentGraph.w < currentGraph.h && new_w < this.MIN_WIDTH) {
            new_w = this.MIN_WIDTH;
            new_h = this.MIN_WIDTH * currentGraph.h / currentGraph.w;
        } else if (currentGraph.h < currentGraph.w && new_h < this.MIN_WIDTH) {
            new_h = this.MIN_WIDTH;
            new_w = this.MIN_WIDTH * currentGraph.w / currentGraph.h;
        }

        this.w = new_w;
        this.h = new_h;
        this.x = currentGraph.x - (new_w - currentGraph.w) / 2;
        this.y = currentGraph.y - (new_h - currentGraph.h) / 2;

    } else if (this.type === 'text') {
        const fontSize = currentGraph.fontSize * ((lineB - lineA) / lineA + 1);
        this.fontSize = fontSize <= this.MIN_FONTSIZE ? this.MIN_FONTSIZE : fontSize;

        // 旋转位移后重新计算坐标
        this.ctx.setFontSize(this.fontSize);
        const textWidth = this.ctx.measureText(this.text).width;
        const textHeight = this.fontSize + 10;
        this.w = textWidth;
        this.h = textHeight;
        // 字体区域中心点不变,左上角位移
        this.x = this.centerX - textWidth / 2;
        this.y = this.centerY - textHeight / 2;
    }
},
toPx(rpx) {
    return rpx * this.factor;
},

}; Component({ /** * 组件的属性列表 */ properties: { graph: { type: Object, value: {}, observer: 'onGraphChange', }, bgColor: { type: String, value: '', }, bgImage: { type: String, value: '', }, bgSourceId: { type: String, value: '', }, width: { type: Number, value: 750, }, height: { type: Number, value: 750, }, alpha:{ type:Number, value:0.6, } },

/**
 * 组件的初始数据
 */
data: {},

attached() {
    const sysInfo = wx.getSystemInfoSync();
    const screenWidth = sysInfo.screenWidth;
    this.factor = screenWidth / 750;

    if (typeof this.drawArr === 'undefined') {
        this.drawArr = [];
    }
    this.ctx = wx.createCanvasContext('canvas-label', this);
    this.draw();
},

/**
 * 组件的方法列表
 */
methods: {
    toPx(rpx) {
        return rpx * this.factor;
    },
    onGraphChange(n, o) {
        if (JSON.stringify(n) === '{}') return;
        this.drawArr.push(new dragGraph(Object.assign({
            x: 30,
            y: 30,
        }, n), this.ctx, this.factor));
        this.draw();
    },
    initByArr(newArr) {
        this.drawArr = [];
        // 循环插入 drawArr
        newArr.forEach((item, index) => {
            switch (item.type) {
                case 'bgColor':
                    this.data.bgImage = '';
                    this.data.bgSourceId = '';
                    this.data.bgColor = item.color;
                    break;
                case 'bgImage':
                    this.data.bgColor = '';
                    this.data.bgImage = item.url;
                    if (item.sourceId) {
                        this.data.bgSourceId = item.sourceId;
                    }
                    break;
                case 'image':
					console.log(item.sourceId);
                case 'text':
                    if (index === newArr.length - 1) {
                        item.selected = true;
                    } else {
                        item.selected = false;
                    }
                    this.drawArr.push(new dragGraph(item, this.ctx, this.factor));
                    break;
            }

        });
        this.draw();
    },
    draw() {
        if (this.data.bgImage !== '') {
            this.ctx.drawImage(this.data.bgImage, 0, 0, this.toPx(this.data.width), this.toPx(this.data.height));
        }
        if (this.data.bgColor !== '') {
            this.ctx.save();
            this.ctx.setFillStyle(this.data.bgColor);
            this.ctx.fillRect(0, 0, this.toPx(this.data.width), this.toPx(this.data.height));
            this.ctx.restore();
        }
		console.log("draw");
        this.drawArr.forEach((item) => {
            item.paint();
        });
        return new Promise((resolve) => {
            this.ctx.draw(false, () => {
                resolve();
            });
        });
    },
    start(e) {
        const {x, y} = e.touches[0];
        this.tempGraphArr = [];
        let lastDelIndex = null; // 记录最后一个需要删除的索引
        //****************新加代码**********************
		let change_pos_idx = -1;
        //****************新加代码结束**********************
        this.drawArr && this.drawArr.forEach((item, index) => {
            const action = item.isInGraph(x, y);
            if (action) {
                item.action = action;
                this.tempGraphArr.push(item);
                // 保存点击时的坐标
                this.currentTouch = {x, y};
                if (action === 'del') {
					console.log(item.sourceId);//xmcase

					const myEventDetail = {delid:item.sourceId} // detail对象,提供给事件监听函数
					const myEventOption = {} // 触发事件的选项
					this.triggerEvent('myevent', myEventDetail, myEventOption);

                    lastDelIndex = index;// 标记需要删除的元素
                }

                //****************新加代码********************** 
				//如果点的不是删除,同时点的是其它层,则要把这层置顶
				if(action !== 'del' && index <= this.drawArr.length-1){
					change_pos_idx =index;
				}
                //*************新加代码结束***************** 

            } else {
                item.action = false;
                item.selected = false;
            }
        });

        //****************新加代码**********************
		if (change_pos_idx > -1 && change_pos_idx != this.drawArr.length - 1 && lastDelIndex == null) {
			console.log(change_pos_idx);
			this.drawArr = change_pos(this.drawArr, change_pos_idx, this.drawArr.length - 1);
			this.ctx.clearRect(0, 0, this.toPx(this.data.width), this.toPx(this.data.height));
		}
        //*************新加代码结束*****************

        // 保存点击时元素的信息
        if (this.tempGraphArr.length > 0) {
            for (let i = 0; i < this.tempGraphArr.length; i++) {
                let lastIndex = this.tempGraphArr.length - 1;
                // 对最后一个元素做操作
                if (i === lastIndex) {
                    // 未选中的元素,不执行删除和缩放操作
                    if (lastDelIndex !== null && this.tempGraphArr[i].selected) {
                        if (this.drawArr[lastDelIndex].action === 'del') {
                            this.drawArr.splice(lastDelIndex, 1);
							lastDelIndex = null;
                            this.ctx.clearRect(0, 0, this.toPx(this.data.width), this.toPx(this.data.height));
                        }
                    } else {
                        this.tempGraphArr[lastIndex].selected = true;
                        this.currentGraph = Object.assign({}, this.tempGraphArr[lastIndex]);
                    }
                } else {
                    // 不是最后一个元素,不需要选中,也不记录状态
                    this.tempGraphArr[i].action = false;
                    this.tempGraphArr[i].selected = false;
                }
            }
        }


		
        this.draw();
    },
    move(e) {
        const {x, y} = e.touches[0];
        if (this.tempGraphArr && this.tempGraphArr.length > 0) {
            const currentGraph = this.tempGraphArr[this.tempGraphArr.length - 1];
            if (currentGraph.action === 'move') {
                currentGraph.centerX = this.currentGraph.centerX + (x - this.currentTouch.x);
                currentGraph.centerY = this.currentGraph.centerY + (y - this.currentTouch.y);
                // 使用中心点坐标计算位移,不使用 x,y 坐标,因为会受旋转影响。
                if (currentGraph.type !== 'text') {
                    currentGraph.x = currentGraph.centerX - this.currentGraph.w / 2;
                    currentGraph.y = currentGraph.centerY - this.currentGraph.h / 2;
                }
            } else if (currentGraph.action === 'transform') {
                currentGraph.transform(this.currentTouch.x, this.currentTouch.y, x, y, this.currentGraph);
            }
            // 更新4个坐标点(相对于画布的坐标系)
            currentGraph._rotateSquare();

            this.draw();
        }
    },
    end(e) {
        this.tempGraphArr = [];
    },
    export() {
        return new Promise((resolve, reject) => {
            this.drawArr = this.drawArr.map((item) => {
                item.selected = false;
                return item;
            });
            this.draw().then(() => {
                wx.canvasToTempFilePath({
                    canvasId: 'canvas-label',
                    success: (res) => {
                        resolve(res.tempFilePath);
                    },
                    fail: (e) => {
                        reject(e);
                    },
                }, this);
            });
        })
    },
    exportJson() {
        return new Promise((resolve, reject) => {
            let exportArr = this.drawArr.map((item) => {
                item.selected = false;
                switch (item.type) {
                    case 'image':
                        return {
                            type: 'image',
                            url: item.fileUrl,
                            y: item.y,
                            x: item.x,
                            w: item.w,
                            h: item.h,
                            rotate: item.rotate,
                            sourceId: item.sourceId,
                        };
                        break;
                    case 'text':
                        return {
                            type: 'text',
                            text: item.text,
                            color: item.color,
                            fontSize: item.fontSize,
                            y: item.y,
                            x: item.x,
                            w: item.w,
                            h: item.h,
                            rotate: item.rotate,
                        };
                        break;
                }
            });
            if (this.data.bgImage) {
                let tmp_img_config = {
                    type: 'bgImage',
                    url: this.data.bgImage,
                };
                if (this.data.bgSourceId) {
                    tmp_img_config['sourceId'] = this.data.bgSourceId;
                }
                exportArr.unshift(tmp_img_config);
            } else if (this.data.bgColor) {
                exportArr.unshift({
                    type: 'bgColor',
                    color: this.data.bgColor
                });
            }

            resolve(exportArr);
        })
    },
    changColor(color) {
        const selected = this.drawArr.filter((item) => item.selected);
        if (selected.length > 0) {
            selected[0].color = color;
        }
        this.draw();
    },
    changeBgColor(color) {
        this.data.bgImage = '';
        this.data.bgColor = color;
        this.draw();
    },
    changeBgImage(newBgImg) {
        this.data.bgColor = '';
        if (typeof newBgImg == 'string') {
            this.data.bgSourceId = '';
            this.data.bgImage = newBgImg;
        } else {
            this.data.bgSourceId = newBgImg.sourceId;
            this.data.bgImage = newBgImg.url;
        }
        this.draw();
    },
    clearCanvas() {
        this.ctx.clearRect(0, 0, this.toPx(this.data.width), this.toPx(this.data.height));
        this.ctx.draw();
        this.drawArr = [];
        this.data.bgColor = '';
        this.data.bgSourceId = '';
        this.data.bgImage = '';
    }
}

});

xmcase avatar May 16 '19 01:05 xmcase

dist目录呢

zlzym avatar Sep 24 '19 07:09 zlzym

能用画面外面的按钮让选中的图层置底吗?因为大图跟小图切换如果在里面是很难切换的 @jasondu 已经通过getrgap()循环再setrgap()设置。可以实现。 setDown:function(){ var that=this; var crentgap=CanvasDrag.getrgap(); var newdrawArr=[]; var selectedindex=crentgap.length-1; var lastArr=[]; var lastdrawArr=[]; if(crentgap.length>1){ crentgap.forEach((item, index) => { console.log(item.selected); if(item.selected){ console.log(selectedindex) lastArr.push(item); } else{ newdrawArr.push(item); } }); if(lastArr.length==0){ newdrawArr=[]; crentgap.forEach((item, index) => { if(index<selectedindex){ console.log(selectedindex) newdrawArr.push(item); } }); lastArr.push(crentgap[selectedindex]); lastdrawArr=lastArr.concat(newdrawArr); } else{ lastdrawArr=lastArr.concat(newdrawArr); } CanvasDrag.setrgap(lastdrawArr); } },

charrysong avatar Jul 29 '21 03:07 charrysong