fabric库 对齐的8种实现 && 辅助线的实现

前言

  • 这两天公司业务需要这个canvas库,但是中文文档很少,fabricjs官网
  • 但是需求还是要写的,开始整吧
  • 我找的,更多fabric中文文档内容

fabric 添加 辅助线 ?

效果 ?

代码 ?

/**
 * @说明 给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种实现 ?

效果?

代码 ?

/**
 * @说明 用于对齐 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的修改方案,可以给我留言哦,感激不尽 ???

转载于:https://juejin.im/post/5d037024e51d45109725fe6c

你可能感兴趣的:(fabric库 对齐的8种实现 && 辅助线的实现)