fabric庫 對齊的8種實現 && 輔助線的實現

前言


fabric 添加 輔助線 🔥

效果 🍎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();
	});
}
複製代碼

fabric 對齊的8種實現 🔥

效果🍎算法

代碼 🐎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(); // 從新渲染
}
複製代碼

後記

  • 開發了兩天,高中數學知識用到了很多 qwq,可是時間緊迫,由於兩天不僅有這些邏輯,還有其餘的邏輯,上面內容只是抽離出來,能夠分享的,但這樣我想仍是有bug的,但願你們若是有須要,或者有bug的修改方案,能夠給我留言哦,感激涕零 🦀🦀🦀
相關文章
相關標籤/搜索