Fabric.js 实现图片填充、适应、拉伸等元素操作功能

253 阅读6分钟

前言

最近在使用 Fabric.js 开发图形操作项目,元素的布局处理在图形操作的场景中非常常见:居中,放大,移动,旋转,等等等。客户通常在使用的时候会频繁的调整图像的大小或者位置,那么我们就可以提供一些预设好的定位方法,来帮助客户快速的实现定位排版。

本文将详细介绍如何使用Fabric.js实现三种常见的图像缩放模式:填充、适应和拉伸。

填充、适应、拉伸的缩放方案

在深入代码实现之前,让我们先明确这三种缩放模式的具体定义:

1. 填充

填充模式的目标是确保图像完全覆盖目标区域,同时保持原始宽高比。这意味着:

  • 图像将按原比例放大或缩小
  • 图像可能会超出画布边界(部分内容可能不可见)
  • 不会有任何留白区域
  • 适合需要背景图像完全覆盖的场景

填充模式的关键是选择较大的缩放比例,确保图像至少在一个维度上完全覆盖目标区域。

我们可以类比 css 背景中的 background-size: cover属性,它会保持图像的宽高比,同时确保图像覆盖整个容器,可能会导致图像的部分区域不可见。

2. 适应

适应模式的目标是确保整个图像都在目标区域内可见,同时保持原始宽高比。这意味着:

  • 图像将按原比例放大或缩小
  • 图像完全在画布内可见(没有内容会被裁剪)
  • 可能会有留白区域
  • 适合需要展示完整图像的场景

适应模式的关键是选择较小的缩放比例,确保图像在两个维度上都不超出目标区域。

我们可以类比 css 中的 background-size: containobject-fit: contain属性,它会保持图像的宽高比,同时确保整个图像都可见,可能会在容器中留下空白区域。

3. 拉伸

拉伸模式的目标是使图像完全填满目标区域,不保持原始宽高比。这意味着:

  • 图像在水平和垂直方向上独立缩放
  • 图像完全填满画布(没有留白)
  • 不保持原始宽高比(可能导致图像变形)
  • 适合需要填满特定区域且不关心比例的场景

拉伸模式的关键是分别计算水平和垂直方向的缩放比例,使图像在两个维度上都刚好填满目标区域。

我们可以类比 css 中的 background-size: 100% 100%object-fit: fill属性,它会拉伸或压缩图像以完全填满容器,不保持原始宽高比。

Fabric.js 缩放元素的方式

Fabric.js中,对象的缩放不是通过直接修改宽度和高度实现的,而是通过缩放属性来实现的,这点要额外注意。

让我们通过一个简单的测试案例来理解Fabric.js中的缩放机制:

// 创建一个100x100的矩形
const rect = new fabric.Rect({
  width: 100,
  height: 100,
  fill: 'red'
});

// 添加到画布
canvas.add(rect);

// 查看原始尺寸
console.log("原始尺寸:", rect.width, rect.height); // 输出: 100 100
console.log("原始缩放:", rect.scaleX, rect.scaleY); // 输出: 1 1
console.log("实际显示尺寸:", rect.width * rect.scaleX, rect.height * rect.scaleY); // 输出: 100 100

// 设置缩放因子为2
rect.set({
  scaleX: 2,
  scaleY: 2
});
canvas.renderAll();

// 查看缩放后的尺寸
console.log("缩放后原始尺寸:", rect.width, rect.height); // 输出: 100 100 (原始尺寸不变)
console.log("缩放因子:", rect.scaleX, rect.scaleY); // 输出: 2 2
console.log("实际显示尺寸:", rect.width * rect.scaleX, rect.height * rect.scaleY); // 输出: 200 200

image.png

image.png

从上面的例子可以看出,当我们设置scaleXscaleY为2时,对象在画布上的实际显示尺寸变为原来的两倍,但原始的widthheight属性保持不变。这种机制使得我们可以轻松地实现各种缩放效果,同时保留原始尺寸信息。

对于图像对象,这一机制同样适用:

fabric.Image.fromURL('image.jpg', img => {
  // 假设图像原始尺寸为200x150
  console.log("图像原始尺寸:", img.width, img.height); // 输出: 200 150
  
  // 设置缩放因子,使图像显示为原来的一半大小
  img.set({
    scaleX: 0.5,
    scaleY: 0.5
  });
  
  canvas.add(img);
  console.log("实际显示尺寸:", img.width * img.scaleX, img.height * img.scaleY); // 输出: 100 75
});

实现方案

核心思路

实现这三种缩放模式的核心在于如何计算合适的scaleXscaleY值。我们可以设计一个通用函数,根据不同的模式计算并应用缩放因子:

  1. 获取画布和对象的尺寸
  2. 计算宽高比
  3. 根据不同模式计算缩放因子
  4. 应用缩放并居中对象

代码实现

首先,我们需要一些辅助函数来获取画布尺寸和居中对象:

/**
 * 获取画布尺寸
 */
function getCanvasDimensions() {
  if (!canvas.value) return { width: 800, height: 600 }
  return {
    width: canvas.value.width || 800,
    height: canvas.value.height || 600,
  }
}

/**
 * 将对象居中到画布中央
 */
function centerObject(
  obj: fabric.Object,
  canvasWidth: number,
  canvasHeight: number,
) {
  obj.setPositionByOrigin(
    new fabric.Point(canvasWidth / 2, canvasHeight / 2),
    'center',
    'center',
  )

  if (canvas.value) {
    canvas.value.renderAll()
  }
}

然后,我们实现一个通用的缩放函数,根据不同的模式计算并应用缩放因子:

/**
 * 调整对象大小并居中的通用函数
 * @param mode 调整模式:'fill' 填充模式,'fit' 适应模式,'stretch' 拉伸模式
 */
function resizeAndCenterObject(mode: 'fill' | 'fit' | 'stretch') {
  const obj = canvas.value?.getActiveObject()
  if (!obj || !canvas.value) return

  // 获取画布尺寸
  const { width: canvasWidth, height: canvasHeight } = getCanvasDimensions()

  // 计算对象和画布的宽高比
  const objRatio = obj.width! / obj.height!
  const canvasRatio = canvasWidth / canvasHeight

  let scaleX, scaleY

  // 计算各种模式下的缩放比例
  if (mode === 'fill') {
    // 填充模式:对象需要完全覆盖画布,可能会超出画布边界
    // 选择较大的缩放比例,确保覆盖整个画布
    if (objRatio < canvasRatio) {
      // 对象较窄,以宽度为基准进行缩放
      scaleX = canvasWidth / obj.width!
      scaleY = scaleX // 保持原比例
    } else {
      // 对象较宽,以高度为基准进行缩放
      scaleY = canvasHeight / obj.height!
      scaleX = scaleY // 保持原比例
    }
  } else if (mode === 'fit') {
    // 适应模式:对象完全在画布内,可能有留白
    // 选择较小的缩放比例,确保对象完全可见
    const fitScaleX = canvasWidth / obj.width!
    const fitScaleY = canvasHeight / obj.height!
    const scale = Math.min(fitScaleX, fitScaleY)
    scaleX = scale
    scaleY = scale
  } else if (mode === 'stretch') {
    // 拉伸模式:宽度和高度都刚好撑满画布,不保持原始比例
    scaleX = canvasWidth / obj.width!
    scaleY = canvasHeight / obj.height!
  }

  // 应用缩放
  obj.set({
    scaleX,
    scaleY,
  })

  // 居中对象
  centerObject(obj, canvasWidth, canvasHeight)
  
  // 取消激活后重新激活对象,确保控制点正确显示
  canvas.value?.discardActiveObject()
  canvas.value?.setActiveObject(obj)
  canvas.value?.renderAll()
}

最后,我们为每种模式创建简单的包装函数,以提高代码的可读性:

// 填充模式 - 将选中对象填充至画布大小(可能超出画布边界)
function applyFill() {
  resizeAndCenterObject('fill')
}

// 适应模式 - 将选中对象适应画布大小(完全在画布内)
function applyFit() {
  resizeAndCenterObject('fit')
}

// 拉伸模式 - 拉伸对象以填满画布(不保持比例)
function applyStretch() {
  resizeAndCenterObject('stretch')
}

测试

添加一张图片

image.png

填充

image.png

适应

image.png

拉伸

image.png

总结

本文介绍了如何使用Fabric.js实现三种常见的图像缩放模式:填充、适应和拉伸。通过理解Fabric.js的缩放机制,实现了这三种方法。

这种实现方式不仅适用于图像,还可以应用于任何Fabric.js对象,如矩形、文本等。