github 地址: https://github.com/wangyuheng/painter
DEMO 地址: http://painter.crick.wang/
变形
既然实现了拖拽效果,就可以在此基础上,实现另一个效果:变形。
HandlerBorder
在实现变形效果之前,先讲解一下 HandlerBorder。这是在 pick 时,选中元素后,在元素周围出现的 4 个黑框。 现在将其扩展为 8 个黑框,拖拽黑框,实现元素的变形效果。
先分析一下 HandlerBorder 的代码
(function() {
var sideLength = 8;
var sideWidth = {
width: 1
};
var HandleBorder = function(svgDoc) {
this.init(svgDoc);
}
HandleBorder.prototype = {
constructor: HandleBorder,
init: function(svgDoc) {
this.currentSvgDoc = svgDoc;
this.create();
return this;
},
};
HandleBorder.prototype.create = function() {
var _this = this;
_this.handleBorderGroup = _this.currentSvgDoc.group();
_this.blockGroup = _this.handleBorderGroup.group();
_this.rectLeftTop = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth);
_this.rectLeftBottom = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth);
_this.rectRightTop = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth);
_this.rectRightBottom = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth);
_this.rectLeftCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth);
_this.rectRightCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth);
_this.rectTopCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth);
_this.rectBottomCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth);
};
HandleBorder.prototype.rebound = function(bbox) {
var _this = this;
var x1 = bbox.x;
var y1 = bbox.y;
var x2 = bbox.x2;
var y2 = bbox.y2;
_this.rectLeftTop.move(x1 - sideLength, y1 - sideLength);
_this.rectLeftBottom.move(x1 - sideLength, y2);
_this.rectRightTop.move(x2, y1 - sideLength);
_this.rectRightBottom.move(x2, y2);
_this.rectLeftCenter.move(x1 - sideLength, (y2 + y1 - sideLength) / 2);
_this.rectRightCenter.move(x2, (y2 + y1 - sideLength) / 2);
_this.rectTopCenter.move((x2 + x1 - sideLength) / 2, y1 - sideLength);
_this.rectBottomCenter.move((x2 + x1 - sideLength) / 2, y2);
};
HandleBorder.prototype.show = function(svgEle) {
if (!svgEle) {
return;
}
this.currentElement = svgEle;
this.handleBorderGroup.show();
this.rebound(svgEle.bbox());
};
HandleBorder.prototype.hide = function() {
this.handleBorderGroup.hide();
};
this.HandleBorder = HandleBorder;
})();
- HandleBorder 有一个构造函数,调用 create 方法。create 方法中创建了一个 group,并在 group 中创建 8 个矩形。
- 在调用 HandleBorder 时,先 new 一个实例,然后调用其 show 方法,先将 8 个矩形的 group 显示出来,再重新设定其位置 rebound。
- rebound 方法根据元素边框坐标和小矩形的边长,计算其位置,分布在 4 个边角与 4 个边长中点。
变形原理
- 将 HandleBorder 中的 8 个小矩形,作为操作框,绑定可拖拽效果。
- 拖拽时,判断其移动距离与方向,使图形对应改变宽高,达到变形效果。
- 为了避免拖拽时,操作框移动到任意位置,每次拖拽时,重新设置其位置 rebound。
代码实现
实现拖拽
小矩形增加.draggable()方法调用。
_this.rectLeftTop = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectLeftBottom = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectRightTop = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectRightBottom = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectLeftCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectRightCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectTopCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectBottomCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
绑定拖拽事件
以左侧中心点操作框 rectLeftCenter 为例
在 dragstart 时,获取鼠标所在坐标 lastPoint
var lastPoint = null;
_this.rectLeftCenter.on("dragstart", function(event) {
lastPoint = event.detail.p;
});
event.detail 中包含 4 个成员变量,可以参考 svg.draggable.js 中的代码
this.el.fire('dragstart', {event: e, p: this.startPoints.point, m: this.m, handler: this})
绑定 dragmove 事件,并重新计算鼠标坐标。
_this.rectLeftCenter.on("dragmove", function() {
var currPoint = event.detail.p;
lastPoint = currPoint;
});
计算图形改变
dragmove 时可以活动当前坐标和上次坐标,计算出 x 轴移动的距离
var dx = currPoint.x - lastPoint.x;
定义一个变量 xLeft = 1;表明鼠标移动方向,向左移动,则为 1, 否则为-1; 并在拖拽开始时,初始化
_this.rectLeftCenter.on("dragstart", function(event) {
lastPoint = event.detail.p;
xLeft = 1;
});
新的元素宽度为
var width = ele.width() - xLeft * dx;
判断 width 是否大于 0,如果大于 0,则表示正向移动, 则新坐标 x 为
var newX = ele.x() + xLeft * dx;
重新设定元素的 x 坐标和 width,即可实现变形效果。
ele.x(newX).width(width);
如果为反向移动,则 width 小于 0,此时反转 xLeft,坐标原点应为元素的 bbox 的 x2 点,并根据 x 的中点做 flip 翻转。
xLeft = -xLeft;
ele.x(ele.bbox().x2).width(-width).matrix(new SVG.Matrix(ele).flip('x', ele.bbox().cx));
反转坐标复位
翻转之后的 rebound 无法复原,需要在 rebound 同样执行矩阵。
this.blockGroup.matrix(new SVG.Matrix(_this.currentElement));
限制拖拽轨迹
为了避免拖拽时,小矩形可以任意移动,则在拖拽时,执行 rebound 操作校准。svg.draggable.js 默认为提供此事件,扩展 svg.draggable.js 增加如下方法。在 DragHander.prototype.drag 的结尾处增加
// so we can use it in the end-method, too
this.el.fire('afterdragmove', { event: e, p: p, m: this.m, handler: this });
并在小矩形中监听次事件,执行复位校准。
_this.rectLeftCenter.on("afterdragmove", function() {
_this.rebound(_this.currentElement.bbox());
});
代码
handle.border.js 完整代码如下
(function() {
var sideLength = 8;
var sideWidth = {
width: 1
};
var HandleBorder = function(svgDoc) {
this.init(svgDoc);
}
HandleBorder.prototype = {
constructor: HandleBorder,
init: function(svgDoc) {
this.currentSvgDoc = svgDoc;
this.create();
return this;
},
};
HandleBorder.prototype.create = function() {
var _this = this;
_this.handleBorderGroup = _this.currentSvgDoc.group();
_this.blockGroup = _this.handleBorderGroup.group();
_this.rectLeftTop = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectLeftBottom = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectRightTop = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectRightBottom = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectLeftCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectRightCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectTopCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
_this.rectBottomCenter = this.blockGroup.rect(sideLength, sideLength).stroke(sideWidth).draggable();
var lastPoint = null;
var xLeft;
_this.rectLeftCenter.on("dragstart", function(event) {
lastPoint = event.detail.p;
xLeft = 1;
});
_this.rectLeftCenter.on("dragmove", function() {
var currPoint = event.detail.p;
var currPoint = event.detail.p;
var dx = currPoint.x - lastPoint.x;
var ele = _this.currentElement;
var width = ele.width() - xLeft * dx;
if (width > 0) {
var newX = ele.x() + xLeft * dx;
ele.x(newX).width(width);
} else {
//invert
xLeft = -xLeft;
ele.x(ele.bbox().x2).width(-width).matrix(new SVG.Matrix(ele).flip('x', ele.bbox().cx));
}
lastPoint = currPoint;
});
_this.rectLeftCenter.on("afterdragmove", function() {
_this.rebound(_this.currentElement.bbox());
});
};
HandleBorder.prototype.rebound = function(bbox) {
var _this = this;
var x1 = bbox.x;
var y1 = bbox.y;
var x2 = bbox.x2;
var y2 = bbox.y2;
_this.rectLeftTop.move(x1 - sideLength, y1 - sideLength);
_this.rectLeftBottom.move(x1 - sideLength, y2);
_this.rectRightTop.move(x2, y1 - sideLength);
_this.rectRightBottom.move(x2, y2);
_this.rectLeftCenter.move(x1 - sideLength, (y2 + y1 - sideLength) / 2);
_this.rectRightCenter.move(x2, (y2 + y1 - sideLength) / 2);
_this.rectTopCenter.move((x2 + x1 - sideLength) / 2, y1 - sideLength);
_this.rectBottomCenter.move((x2 + x1 - sideLength) / 2, y2);
this.blockGroup.matrix(new SVG.Matrix(_this.currentElement));
};
HandleBorder.prototype.show = function(svgEle) {
if (!svgEle) {
return;
}
this.currentElement = svgEle;
this.handleBorderGroup.show();
this.rebound(svgEle.bbox());
};
HandleBorder.prototype.hide = function() {
this.handleBorderGroup.hide();
};
this.HandleBorder = HandleBorder;
})();
附
涉及到数学思维,总是绕不出去,只能不断的试错。
为每个操作框绑定事件太繁琐,是否可以通用?
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于