网站搜索

javascript - 推广寻找矩形四边最小尺寸解的方法


在一个应用程序中,用户可以点击矩形框的边缘,将其在该方向上调整到“最小有效尺寸”。例如,如果他们点击右边缘,框将调整到最小的有效宽度,同时保持左边缘不动。如果他们点击底部,底边会尽可能向上收缩而不失效。“有效”的标准无关紧要,但计算起来需要一些工作。

现在我已经写了一些代码,使其可以用于盒子的右边缘,使用二分查找来缩小最小有效尺寸。但我需要一个通用的解决方案,不涉及在四个不同的变体中重复我的代码,而我又想不出如何做到这一点。

现在的代码(仅支持右边缘):

interface Zone {
  x: number;
  y: number;
  width: number;
  height: number;
}

enum Side {
  'top',
  'left',
  'bottom',
  'right',
}

enum ResizeDirection {
  NEGATIVE = -1,
  POSITIVE = 1,
}

async function findMinValidZone(
  zone: Zone,
  minWidth: number,
  maxWidth: number,
  side: Side,
  validator: (z: Zone) => Promise<boolean>
): Promise<Zone> {
  const area = {...zone};
  let d = maxWidth;
  let step = maxWidth - minWidth;
  let currentDirection = ResizeDirection.NEGATIVE; // positive means widen the area, negative means narrow it
  let lastGood: number = d;
  do {
    d += step * currentDirection;
    if (d < minWidth || d > maxWidth) {
      break;
    }
    area.width = d;
    const ok = await validator(area);
    if (ok) {
      currentDirection = ResizeDirection.NEGATIVE;
      lastGood = d;
    } else {
      currentDirection = ResizeDirection.POSITIVE;
    }
    step = step / 2;
  } while (step >= 1);
  area.width = lastGood;
  return area;
}

答案#1

我能想到的一种方法是将矩形旋转所需的 90 度倍数,以便将矩形转换,使你总是处理右边缘,然后在验证和返回结果时将其旋转回去。实际旋转可以使用矩阵变换来完成,但现在我会把实际的旋转函数留给你来编写。然后你的通用代码看起来会像这样:

function findMinValidZone(
  zone: Zone,
  minwidth: number,
  maxWidth: number,
  side: Side,
  validator: (z: Zone) => Promise<boolean>
): Promise<Zone> {
  let rotationAngle: number;
  switch (side) {
    case Side.bottom:
      rotationAngle = -Math.PI / 2;
      break;
    case Side.left:
      rotationAngle = Math.PI;
      break;
    case Side.right:
      rotationAngle = 0;
      break;
    case Side.top:
      rotationAngle = Math.PI / 2;
  }
  let d = maxWidth;
  let step = maxWidth - minwidth;
  // rotate the area so that you are always working on the width dimension
  // while keeping other dimensions fixed. Then rotate area back again
  // when validating and when returning the results
  const area = rotateZone({...zone}, rotationAngle);
  let currentDirection = ResizeDirection.NEGATIVE; // positive means widen the area, negative means narrow it
  let lastGood: number = d;
  do {
    d += step * currentDirection;
    if (d < minwidth || d > maxWidth) {
      break;
    }
    area.width = d;
    const ok = await validator(rotateZone(area, -rotationAngle));
    if (ok) {
      currentDirection = ResizeDirection.NEGATIVE;
      lastGood = d;
    } else {
      currentDirection = ResizeDirection.POSITIVE;
    }
    step = step / 2;
  } while (step >= 1);
  area.width = lastGood;
  return rotateZone(area, -rotationAngle);
}

我会考虑 rotateZone 函数本身,但原则上使用像 https://www.npmjs.com/package/transformation-matrix 这样的库应该足够简单