Задать вопрос
@veroni1337

Как добавить ограничение на перемещение с transform translate?

Я делаю редактор на реакт. Элементы холста генерируются с помощью html без канваса. Добавил функцию перемещения по холсту с помощью transform translate. Теперь не могу понять как добавить ограничение если пользователь докрутил до границы холста, сейчас он может бесконечно крутить.

Размеры экрана в котором находится холст может быть разным в зависимости от размера экрана. Размеры самого холста всегда 1920 на 1080 (почти всегда больше чем родительский див).

Вот код перемещения и мои попытки добавить ограничение + jsx разметка:
// drag
const [space, setSpace] = useState(false);
const [origin, setOrigin] = useState({ x: 0, y: 0 });
const [translate, setTranslate] = useState({ x: 0, y: 0 });

useEffect(() => {
  const scrollDown = (event: KeyboardEvent): void => {
    if (event.code === 'Space') {
      setSpace(true);
    }
  };

  const scrollUp = (event: KeyboardEvent): void => {
    if (event.code === 'Space') {
      setSpace(false);
    }
  };
  window.addEventListener('keydown', scrollDown);
  window.addEventListener('keyup',   scrollUp);
  return () => {
    window.removeEventListener('keydown', scrollDown);
    window.removeEventListener('keyup',   scrollUp);
  };
}, []);

// ограничитель drag
const clampTranslate = 
  ( x: number, y: number ): 
  { x: number; y: number } | false => {
  const wrapper = scrollWrapperRef.current;
  const canvasWrapper = canvas.current;
  if (!wrapper && !canvasWrapper) return { x, y };

  const rect = wrapper.getBoundingClientRect();
  const canvasRect = canvasWrapper.getBoundingClientRect();

  const rectMaxX = rect.left + 50; // слева
  const rectMinX = rect.right + 50; // справа
  const rectMaxY = rect.top + 50; // сверху
  const rectMinY = rect.bottom + 50; // вниз

  console.log(rectMaxX, rectMinX, rectMaxY, rectMinY, x, y, canvasRect);

  if (canvasRect.right < rectMinX) {
    console.log('Вы вышли за пределы правого края');
  }

  if (canvasRect.left > rectMaxX) {
    console.log('Вы вышли за пределы левого края');
  }

  if (canvasRect.top > rectMaxY) {
    console.log('Вы вышли за пределы верхнего края', event);
  }

  if (canvasRect.bottom < rectMinY) {
    console.log('Вы вышли за пределы нижнего края');
  }

  console.log(maxX, minX, maxY, minY);
  
  return {
    x: x,
    y: y,
  };
};

// трекпад
const handleWheel = (event: React.WheelEvent): void => {
  setIsDragging(true);

  setTranslate((prev) => {
    const next = { x: prev.x - event.deltaX, y: prev.y - event.deltaY };
    return clampTranslate(next.x, next.y);
  });
};

const handleMouseDown = (event: React.MouseEvent): void => {
  if (!space) return;
  setIsDragging(true);
  setOrigin({ x: event.clientX - translate.x, y: event.clientY - translate.y });

  event.preventDefault();
};

const handleMouseMove = (event: React.MouseEvent): void => {
  if (!space) return;
  if (!isDragging) return;
  const newX = event.clientX - origin.x;
  const newY = event.clientY - origin.y;
  
  setTranslate(clampTranslate(newX, newY));
};

const handleMouseUp = (): void => {
  setIsDragging(false);
};

<StyledEditorPageContent>
  <StyledEditorWrapper
    ref={scrollWrapperRef}
    onWheel={handleWheel}
    onMouseDown={handleMouseDown}
    onMouseMove={handleMouseMove}
    onMouseUp={handleMouseUp}
    onMouseLeave={handleMouseUp}
  >
    <StyledEditorCanvasWrapper
      key={artBoard?.id}
      id={canvasId}
      style={{
        transform: `translate(${translate.x}px, ${translate.y}px)`,
      }}
    >
      <StyledEditorCanvas
        ref={canvas}
        id={"canvas-content"}
        isHorizontal={currentOrientationCanvas === orientationsIds.horizontal}
        cursor={getCursorCanvas()}
        onClick={handleCanvasClick}
      >
        {(draft || [])?.map((item, index) => {
          return (
            <EditorLayer
              key={`${item.id}-${index}`}
              {...item}
              onClick={(): void => {
                handleLayerClick(item.id);
              }}
              index={index}
              disableDragging={isDragging}
            />
          );
        })}
      </StyledEditorCanvas>
    </StyledEditorCanvasWrapper>
  </StyledEditorWrapper>
</StyledEditorPageContent>;


В функции clampTranslate я могу с помощью координат родительского дива и холста узнать пересек ли пользователь границу, но не могу понять как мне ограничить движение. Если добавлю return false, то перемещение заблокируется
  • Вопрос задан
  • 53 просмотра
Подписаться 1 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 2
VoidVolker
@VoidVolker Куратор тега JavaScript
Dark side eye. А у нас печеньки! А у вас?
Используйте Math.min() и Math.max() для ограничений координат.
Ответ написан
Комментировать
@Aetheris666
Вот исправленный код с ограничением перемещения холста:
const clampTranslate = (
x: number,
y: number
): { x: number; y: number } => {
const wrapper = scrollWrapperRef.current;
const canvasWrapper = canvas.current;

if (!wrapper || !canvasWrapper) return { x, y };

const wrapperRect = wrapper.getBoundingClientRect();
const canvasWidth = canvasWrapper.offsetWidth;
const canvasHeight = canvasWrapper.offsetHeight;

const maxTranslateX = 0;
const minTranslateX = -(canvasWidth - wrapperRect.width);
const maxTranslateY = 0;
const minTranslateY = -(canvasHeight - wrapperRect.height);

return {
x: Math.max(minTranslateX, Math.min(maxTranslateX, x)),
y: Math.max(minTranslateY, Math.min(maxTranslateY, y)),
};
};

const handleWheel = (event: React.WheelEvent): void => {
setIsDragging(true);

setTranslate((prev) => {
const next = {
x: prev.x - event.deltaX,
y: prev.y - event.deltaY
};
return clampTranslate(next.x, next.y);
});
};

const handleMouseMove = (event: React.MouseEvent): void => {
if (!space || !isDragging) return;

const newX = event.clientX - origin.x;
const newY = event.clientY - origin.y;

setTranslate(clampTranslate(newX, newY));
};

// Остальной код остается без изменений

Основные исправления:

В функции clampTranslate теперь корректно вычисляются границы перемещения:
Максимальное перемещение = 0 (исходная позиция)
Минимальное перемещение = -(размер_холста - размер_контейнера)
Добавлено ограничение значений через Math.max() и Math.min()
Убраны лишние проверки и console.log
Сохранена оригинальная логика обработки событий
Функция ограничения теперь работает по принципу:
x = Math.max(минимальное_значение, Math.min(максимальное_значение, x))
Это гарантирует, что значения трансляции всегда остаются в допустимых границах, предотвращая бесконечное перемещение холста.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы