Skip to main content

缩放图像以适应固定的画布区域

如何在不拉伸图像的情况下缩放图像以适应可用区域?

该演示展示了如何使用 Konva.Imagecrop 属性来模拟 CSS 的 object-fit: cover

crop 属性允许您仅使用源图像的指定区域进行画布绘制。如果做出正确的计算,则可以在不拉伸的情况下绘制得到的图像。

说明:尝试调整图像大小或使用顶部下拉菜单更改裁剪策略。图像将在符合指定尺寸的同时保持其纵横比。

import Konva from 'konva';

// 创建选择元素以选择裁剪位置
const select = document.createElement('select');
select.style.position = 'absolute';
select.style.top = '4px';
select.style.left = '4px';

const positions = [
  '左上', '中上', '右上', '--',
  '左中', '中中', '右中', '--',
  '左下', '中下', '右下'
];

positions.forEach(pos => {
  const option = document.createElement('option');
  option.value = pos;
  option.text = pos;
  if (pos === '中中') option.selected = true;
  select.appendChild(option);
});

document.body.appendChild(select);

const stage = new Konva.Stage({
  container: 'container',
  width: window.innerWidth,
  height: window.innerHeight,
});

const layer = new Konva.Layer();
stage.add(layer);

// 计算源图像、其可见大小和裁剪策略的裁剪值的函数
function getCrop(image, size, clipPosition = 'center-middle') {
  const width = size.width;
  const height = size.height;
  const aspectRatio = width / height;

  let newWidth;
  let newHeight;

  const imageRatio = image.width / image.height;

  if (aspectRatio >= imageRatio) {
    newWidth = image.width;
    newHeight = image.width / aspectRatio;
  } else {
    newWidth = image.height * aspectRatio;
    newHeight = image.height;
  }

  let x = 0;
  let y = 0;
  if (clipPosition === '左上') {
    x = 0;
    y = 0;
  } else if (clipPosition === '左中') {
    x = 0;
    y = (image.height - newHeight) / 2;
  } else if (clipPosition === '左下') {
    x = 0;
    y = image.height - newHeight;
  } else if (clipPosition === '中上') {
    x = (image.width - newWidth) / 2;
    y = 0;
  } else if (clipPosition === '中中') {
    x = (image.width - newWidth) / 2;
    y = (image.height - newHeight) / 2;
  } else if (clipPosition === '中下') {
    x = (image.width - newWidth) / 2;
    y = image.height - newHeight;
  } else if (clipPosition === '右上') {
    x = image.width - newWidth;
    y = 0;
  } else if (clipPosition === '右中') {
    x = image.width - newWidth;
    y = (image.height - newHeight) / 2;
  } else if (clipPosition === '右下') {
    x = image.width - newWidth;
    y = image.height - newHeight;
  }

  return {
    cropX: x,
    cropY: y,
    cropWidth: newWidth,
    cropHeight: newHeight,
  };
}

// 应用裁剪的函数
function applyCrop(img, pos) {
  img.setAttr('lastCropUsed', pos);
  const crop = getCrop(
    img.image(),
    { width: img.width(), height: img.height() },
    pos
  );
  img.setAttrs(crop);
}

Konva.Image.fromURL('https://konvajs.org/assets/darth-vader.jpg', (img) => {
  img.setAttrs({
    width: 300,
    height: 100,
    x: 80,
    y: 100,
    name: 'image',
    draggable: true,
  });
  layer.add(img);
  // 应用默认的中中裁剪
  applyCrop(img, '中中');

  const tr = new Konva.Transformer({
    nodes: [img],
    keepRatio: false,
    flipEnabled: false,
    boundBoxFunc: (oldBox, newBox) => {
      if (Math.abs(newBox.width) < 10 || Math.abs(newBox.height) < 10) {
        return oldBox;
      }
      return newBox;
    },
  });

  layer.add(tr);

  img.on('transform', () => {
    // 在变换时重置缩放
    img.setAttrs({
      scaleX: 1,
      scaleY: 1,
      width: img.width() * img.scaleX(),
      height: img.height() * img.scaleY(),
    });
    applyCrop(img, img.getAttr('lastCropUsed'));
  });
});

select.addEventListener('change', (e) => {
  const img = layer.findOne('.image');
  applyCrop(img, e.target.value);
});