效果 🍎git
代碼 🐎github
/** * @說明 給fabirc中的元素加入輔助線(網上資料,修改使用) * @param {canvas} 參數說明:須要傳入 new fabric.Canvas() 的那個對象 * @host https://my.oschina.net/xmqywx/blog/1941539 * @time 2019.6.12 */
export const initAligningGuidelines = canvas => {
var ctx = canvas.getSelectionContext(), // getSelectionContext 獲取選擇上下文
aligningLineOffset = 5, // 對齊線條偏移
aligningLineMargin = 4, // 對齊線邊距
aligningLineWidth = 1, // 對齊線條寬度
aligningLineColor = '#666666', // 顏色
viewportTransform, // 視圖端口轉換
zoom = 1;
// 畫垂直線
function drawVerticalLine(coords) {
drawLine(
coords.x + 0.5,
coords.y1 > coords.y2 ? coords.y2 : coords.y1,
coords.x + 0.5,
coords.y2 > coords.y1 ? coords.y2 : coords.y1);
}
// 畫水平線
function drawHorizontalLine(coords) {
drawLine(
coords.x1 > coords.x2 ? coords.x2 : coords.x1,
coords.y + 0.5,
coords.x2 > coords.x1 ? coords.x2 : coords.x1,
coords.y + 0.5);
}
// 畫線
function drawLine(x1, y1, x2, y2) {
ctx.save();
ctx.lineWidth = aligningLineWidth;
ctx.strokeStyle = aligningLineColor;
ctx.beginPath();
ctx.moveTo(((x1+viewportTransform[4])*zoom), ((y1+viewportTransform[5])*zoom));
ctx.lineTo(((x2+viewportTransform[4])*zoom), ((y2+viewportTransform[5])*zoom));
ctx.stroke();
ctx.restore();
}
// 範圍
function isInRange(value1, value2) {
value1 = Math.round(value1);
value2 = Math.round(value2);
for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
if (i === value2) {
return true;
}
}
return false;
}
var verticalLines = [],
horizontalLines = [];
// 移動
canvas.on('mouse:down', function () {
viewportTransform = canvas.viewportTransform;
zoom = canvas.getZoom();
});
// 對象移動事件(移動到某個點才具備輔助線的功能)
canvas.on('object:moving', function(e) {
var activeObject = e.target,
canvasObjects = canvas.getObjects(),
activeObjectCenter = activeObject.getCenterPoint(),
activeObjectLeft = activeObjectCenter.x,
activeObjectTop = activeObjectCenter.y,
activeObjectBoundingRect = activeObject.getBoundingRect(),
activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
horizontalInTheRange = false,
verticalInTheRange = false,
transform = canvas._currentTransform;
if (!transform) return;
// It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
// but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
for (var i = canvasObjects.length; i--; ) {
if (canvasObjects[i] === activeObject) continue;
var objectCenter = canvasObjects[i].getCenterPoint(),
objectLeft = objectCenter.x,
objectTop = objectCenter.y,
objectBoundingRect = canvasObjects[i].getBoundingRect(),
objectHeight = objectBoundingRect.height / viewportTransform[3],
objectWidth = objectBoundingRect.width / viewportTransform[0];
// snap by the horizontal center line
if (isInRange(objectLeft, activeObjectLeft)) {
verticalInTheRange = true;
verticalLines.push({
x: objectLeft,
y1: (objectTop < activeObjectTop)
? (objectTop - objectHeight / 2 - aligningLineOffset)
: (objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop)
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
});
activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), 'center', 'center');
}
// snap by the left edge
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
verticalInTheRange = true;
verticalLines.push({
x: objectLeft - objectWidth / 2,
y1: (objectTop < activeObjectTop)
? (objectTop - objectHeight / 2 - aligningLineOffset)
: (objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop)
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
});
activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), 'center', 'center');
}
// snap by the right edge
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
verticalInTheRange = true;
verticalLines.push({
x: objectLeft + objectWidth / 2,
y1: (objectTop < activeObjectTop)
? (objectTop - objectHeight / 2 - aligningLineOffset)
: (objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop)
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
});
activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), 'center', 'center');
}
// snap by the vertical center line
if (isInRange(objectTop, activeObjectTop)) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop,
x1: (objectLeft < activeObjectLeft)
? (objectLeft - objectWidth / 2 - aligningLineOffset)
: (objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft)
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
});
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), 'center', 'center');
}
// snap by the top edge
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop - objectHeight / 2,
x1: (objectLeft < activeObjectLeft)
? (objectLeft - objectWidth / 2 - aligningLineOffset)
: (objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft)
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
});
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), 'center', 'center');
}
// snap by the bottom edge
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop + objectHeight / 2,
x1: (objectLeft < activeObjectLeft)
? (objectLeft - objectWidth / 2 - aligningLineOffset)
: (objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft)
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
});
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), 'center', 'center');
}
}
if (!horizontalInTheRange) {
horizontalLines.length = 0;
}
if (!verticalInTheRange) {
verticalLines.length = 0;
}
});
// 結束繪畫事件
canvas.on('before:render', function() {
canvas.clearContext(canvas.contextTop);
});
// 開始繪畫事件(也就是圖形開始變化)
canvas.on('after:render', function() {
for (var i = verticalLines.length; i--; ) {
drawVerticalLine(verticalLines[i]);
}
for (var i = horizontalLines.length; i--; ) {
drawHorizontalLine(horizontalLines[i]);
}
verticalLines.length = horizontalLines.length = 0;
});
canvas.on('mouse:up', function() {
verticalLines.length = horizontalLines.length = 0;
canvas.renderAll();
});
}
複製代碼
效果🍎算法
代碼 🐎canvas
/** * @說明 用於對齊 fabrice元素(適用於v1.7*版本的fabric.js庫) * * @param {canvas} 參數說明:須要傳入 new fabric.Canvas() 的那個對象 * @param {alignparam} 參數說明: * @param top 上對齊(默認值,已實現) * @param left 左對齊(已實現) * @param botton 底對齊(已實現) * @param right 右對齊(已實現) * @param horizontalC 水平居中對齊(已實現) * @param verticalC 垂直居中對齊(已實現) * @param horizontalA 水平平均分佈(已實現) * @param verticalA 垂直平均分佈(已實現) * * @author zkp * @time 2019.6.13 - 2019.6.14 */
export const fabricItemAlign = (canvas, alignparam = 'top') => {
// 實現方案一:經過計算,對頂掉誤差(這裏框架內部計算仍是有很大的問題的,不可控)
// 規定術語說明,選中的好幾個元素,最外層的那一個大的元素稱之爲 Z
// canvas._activeGroup 表示 Z 也就是選中以後的最大的一層
// canvas._activeGroup._objects 爲選中的全部元素
// v3.* 兼容性方案(下面代碼寫的是v1.7*版本的方案)
// ①:canvas._activeGroup._objects 換爲 canvas.getActiveObjects()
// ②:canvas._activeGroup 換爲 canvas._activeObject
// 頂對齊
if (alignparam === 'top') {
// 是以選中框 爲相對位置進行計算的(頂對齊就是 top爲 0 ,左對齊就是 left 0)
// 下面就是計算 剩餘一個軸 相對於 選擇框的相對位置
canvas._activeGroup._objects.forEach(item => {
// 頂對齊算法:top爲零表示 依據選中框頂部對齊, left 爲 選中點每一項的
item.set({
top: 0 - canvas._activeGroup.height * 0.5,
left: (item.aCoords.tl.x - canvas._activeGroup.aCoords.tl.x) - canvas._activeGroup.width * 0.5
})
})
}
// 左對齊
if (alignparam === 'left') {
// 是以選中框 爲相對位置進行計算的(頂對齊就是 top爲 0 ,左對齊就是 left 0)
// 下面就是計算 剩餘一個軸 相對於 選擇框的相對位置
canvas._activeGroup._objects.forEach(item => {
item.set({
top: (item.aCoords.tl.y - canvas._activeGroup.aCoords.tl.y) - canvas._activeGroup.height * 0.5,
left: 0 - canvas._activeGroup.width * 0.5
})
})
}
// 右對齊
if (alignparam === 'right') {
// top公式:與 左對齊一致
// left公式:座標要以 Z 的寬度爲計算,並減去元素自己的寬度
canvas._activeGroup._objects.forEach(item => {
console.log(canvas._activeGroup)
item.set({
top: (item.aCoords.tl.y - canvas._activeGroup.aCoords.tl.y) - canvas._activeGroup.height * 0.5,
left: (canvas._activeGroup.width - item.width) - canvas._activeGroup.width * 0.5
})
})
}
// 底對齊
if (alignparam === 'bottom') {
// top公式:與 左對齊一致
// left公式:與 上對齊一致
canvas._activeGroup._objects.forEach(item => {
// 頂對齊算法:top爲零表示 依據選中框頂部對齊, left 爲 選中點每一項的
item.set({
top: (canvas._activeGroup.height - item.height) - canvas._activeGroup.height * 0.5,
left: (item.aCoords.tl.x - canvas._activeGroup.aCoords.tl.x) - canvas._activeGroup.width * 0.5
})
})
}
// 水平居中對齊
if (alignparam === 'horizontalC') {
// top公式:(Z.heiht - 每一項的元素的.height) 其實就是這個元素相對於 Z 中線的一個居中位置(和CSS定位居中對齊一個原理)
// left公式:保持位置相對不動
canvas._activeGroup._objects.forEach(item => {
item.set({
top: (canvas._activeGroup.height - item.height) * 0.5 - canvas._activeGroup.height * 0.5,
left: (item.aCoords.tl.x - canvas._activeGroup.aCoords.tl.x) - canvas._activeGroup.width * 0.5
})
})
}
// 垂直居中對齊
if (alignparam === 'verticalC') {
// top公式:保持位置相對不動
// left公式:(Z.heiht - 每一項的元素的.height)
canvas._activeGroup._objects.forEach(item => {
item.set({
top: (item.aCoords.tl.y - canvas._activeGroup.aCoords.tl.y) - canvas._activeGroup.height * 0.5,
left: (canvas._activeGroup.width - item.width) * 0.5 - canvas._activeGroup.width * 0.5
})
})
}
// 水平平均分佈
if (alignparam === 'horizontalA') {
// 前置:須要按照 canvas._activeGroup._objects 每一項的 aCoords.tl.x 進行從小到大排列,由於開始結束位置可能不同
// 注意:sort方法排序改變的是原有的數組不生成副本
canvas._activeGroup._objects.sort( (a, b) => {
return a.aCoords.tl.x - b.aCoords.tl.x;
})
// 水平等差間距:
let count = 0;
canvas._activeGroup._objects.forEach(item => { count += item.width })
let es = (canvas._activeGroup.width - count) / (canvas._activeGroup._objects.length - 1)
let sefNum = 0; // 記錄中間過程值
let sefNumOne = true; // 斷定
// console.log('水平等差間距是:', es)
for (let i = 0, len = canvas._activeGroup._objects.length; i < len; i++) {
// 等分排列 開始和結尾不變
if (i !== 0 && i !== len - 1) {
if (sefNumOne) {
// 第二個的計算規則是:第一個的長度 + 等差間距
sefNum += (canvas._activeGroup._objects[i-1].width + es)
sefNumOne = false
} else {
// 其餘項的計算規則是:sefNum(這個至關於前一個的 left值) + 前一個的寬度 + 等差間距
sefNum += es + canvas._activeGroup._objects[i-1].width;
}
console.log('sefNum', sefNum)
// top規則:保持相對位置不變
// left規則:採用 sefNum 的值
canvas._activeGroup._objects[i].set({
top: (canvas._activeGroup._objects[i].aCoords.tl.y - canvas._activeGroup.aCoords.tl.y) - canvas._activeGroup.height * 0.5,
left: sefNum - canvas._activeGroup.width * 0.5
})
}
}
}
// 垂直平均分佈
if (alignparam === 'verticalA') {
// 前置:須要按照 canvas._activeGroup._objects 每一項的 aCoords.tl.x 進行從小到大排列,由於開始結束位置可能不同
// 注意:sort方法排序改變的是原有的數組不生成副本
canvas._activeGroup._objects.sort( (a, b) => {
return a.aCoords.tl.y - b.aCoords.tl.y;
})
// 垂直等差間距:
let count = 0;
canvas._activeGroup._objects.forEach(item => { count += item.height })
let es = (canvas._activeGroup.height - count) / (canvas._activeGroup._objects.length - 1)
let sefNum = 0; // 記錄中間過程值
let sefNumOne = true; // 斷定
// console.log('垂直等差間距:', es)
for (let i = 0, len = canvas._activeGroup._objects.length; i < len; i++) {
// 等分排列 開始和結尾不變
if (i !== 0 && i !== len - 1) {
if (sefNumOne) {
// 第二個的計算規則是:第一個的高度 + 寬度等差間距
sefNum += (canvas._activeGroup._objects[i-1].height + es)
sefNumOne = false
} else {
// 其餘項的計算規則是:sefNum(這個至關於前一個的 top值) + 前一個的高度 + 寬度等差間距
sefNum += es + canvas._activeGroup._objects[i-1].height;
}
// top規則:採用 sefNum 的值
// left規則:保持相對位置不變
canvas._activeGroup._objects[i].set({
top: sefNum - canvas._activeGroup.height * 0.5,
left: (canvas._activeGroup._objects[i].aCoords.tl.x - canvas._activeGroup.aCoords.tl.x) - canvas._activeGroup.width * 0.5
})
}
}
}
// 其餘對齊規則拓展 ......
canvas.renderAll(); // 從新渲染
}
複製代碼