drag and drop, растяжение элемента по ширине

Я делаю такое: https://codepen.io/mihinov/pen/yLegxYv
Там используется localeStorage, такое не пропускает stackoverflow.

Когда я тяну с левого края вправо, у меня блок становится шириной в 0, и я начинаю как бы увеличивать ширину вправо.

Не понимаю как прописать нужное условие.

this.node - это DOM элемент, с которым происходит событие

const leftEdge = this.node.getBoundingClientRect().left; // левый край блока
const rightEdge = this.node.getBoundingClientRect().right; // правый край блока
const x = e.clientX; // позиция мышки по X
let width;
if (x > leftEdge) {
    width = x - leftEdge;
} else {
    this.node.style.left = x + 'px';
    width = rightEdge - x;
}

this.node.style.width = width + 'px';

Ответы (1 шт):

Автор решения: Михаил Камахин

Надо было опираться на середину элемента, а не на его края:

const leftEdge = this.node.getBoundingClientRect().left; // левый край блока
const rightEdge = this.node.getBoundingClientRect().right; // правый край блока
const centerX = leftEdge + this.node.clientWidth / 2;
const x = e.clientX; // позиция мышки по X
let width;
if (x > centerX) {
    width = x - leftEdge;
} else {
    width = rightEdge - x;
    this.node.style.left = x + 'px';
}

this.node.style.width = width + 'px';

class BlockInGame {
  constructor(src = 'move__block', styles) {
    this.global();
    this.src = src;
    this.styles = styles;
    this.node = document.createElement('div');
    this.node.classList.add(src);
    this.addInBody();
    this.listeners();
    if (!this.constructor.block_counter) {
      this.constructor.block_counter = 1
    } else {
      this.constructor.block_counter++;
    }
    this.node.id = 'move__block-' + this.constructor.block_counter;
    this.addStyles(styles);
    setTimeout(() => {
      this.nodeElemsThisClass = [...this.global.body.querySelectorAll('.' + this.src)];
    }, 50);
  }
  listeners() {
    this.node.ondragstart = () => (false);
    const border_definition = (x_OR_y, width_0R_height, borderWidth) => {
      return (x_OR_y > width_0R_height - borderWidth || x_OR_y < borderWidth);
    }
    let clickOnTheBorder = false;
    const beforeClicking = (e) => {
      const width = this.node.offsetWidth;
      const height = this.node.offsetHeight;
      const borderWidth = 5;
      const x = e.offsetX;
      const y = e.offsetY;
      if (border_definition(x, width, borderWidth)) {
        this.node.style.cursor = 'ew-resize';
        clickOnTheBorder = {
          axis: 'x'
        };
        return;
      } else {
        clickOnTheBorder = true;
        this.node.style.cursor = 'pointer';
      }

      if (border_definition(y, height, borderWidth)) {
        this.node.style.cursor = 'ns-resize';
        clickOnTheBorder = {
          axis: 'y'
        };
        return;
      } else {
        clickOnTheBorder = true;
        this.node.style.cursor = 'pointer';
      }
    };
    const stretching = () => {
      const mouseMoveStretching = (e) => {
        if (clickOnTheBorder != false) {
          if (clickOnTheBorder.axis === 'x') {
            const leftEdge = this.node.getBoundingClientRect().left; // левый край блока
            const rightEdge = this.node.getBoundingClientRect().right; // правый край блока
            const centerX = leftEdge + this.node.clientWidth / 2;
            const x = e.clientX; // позиция мышки по X
            let width;
            if (x > centerX) {
              width = x - leftEdge;
            } else {
              width = rightEdge - x;
              this.node.style.left = x + 'px';
            }

            this.node.style.width = width + 'px';
            return;
          } else if (clickOnTheBorder.axis === 'y') {
            const topEdge = this.node.getBoundingClientRect().top;
            const bottomEdge = this.node.getBoundingClientRect().bottom;
            const centerY = topEdge + this.node.clientHeight / 2;
            const y = e.clientY;
            let height;
            if (y > centerY) {
              height = y - topEdge;
            } else {
              height = bottomEdge - y;
              this.node.style.top = y + 'px';
            }

            this.node.style.height = height + 'px';
            return;
          }
        }
      };
      this.global.game.addEventListener('mousemove', mouseMoveStretching);
      this.global.game.addEventListener('mouseup', () => {
        this.global.game.removeEventListener('mousemove', mouseMoveStretching);
        this.node.addEventListener('mousemove', beforeClicking);
      });
    };
    this.node.addEventListener('mousemove', beforeClicking);
    this.node.addEventListener('mousedown', () => {
      this.node.removeEventListener('mousemove', beforeClicking);
      this.nodeElemsThisClass.forEach((item) => {
        item.style.zIndex = '10';
      });
      this.node.style.zIndex = '20';
      if (clickOnTheBorder !== true) {
        return stretching();
      }
      this.node.style.cursor = 'move';
      this.shift = {
        x: event.clientX - this.node.getBoundingClientRect().left,
        y: event.clientY - this.node.getBoundingClientRect().top
      };
      const localMouseupFunc = () => {
        this.global.game.removeEventListener('mousemove', onMouseMoveBind);
        this.global.game.removeEventListener('mouseup', localMouseupFunc);
        this.node.style.cursor = 'pointer';
        this.node.addEventListener('mousemove', beforeClicking);
      }
      const onMouseMoveBind = this.onMouseMove.bind(this);
      this.global.game.addEventListener('mousemove', onMouseMoveBind);
      this.global.game.addEventListener('mouseup', localMouseupFunc);
    });
    const heightAndWidthGame = this.heightAndWidthGame.bind(this);
    heightAndWidthGame();
    window.addEventListener('resize', heightAndWidthGame);
  }

  global() {
    if (!this.global.body) {
      this.global.body = document.querySelector('body');
    }
    if (!this.global.game) {
      this.global.game = this.global.body.querySelector('.game');
    }
  }

  heightAndWidthGame() {
    this.widthGame = this.global.game.offsetWidth;
    this.heightGame = this.global.game.offsetHeight;
  }

  addStyles(styles) {
    for (let key in styles) {
      this.node.style[key] = styles[key];
    }
    this.node.style.position = 'absolute';
    this.node.style.cursor = 'pointer';
    this.node.style.zIndex = '10';
  }

  addInBody() {
    this.global.game.append(this.node);
  }

  move(page) {
    const pageX = page.x - this.shift.x; // Позиция мышки по X
    const pageY = page.y - this.shift.y; // Позиция мышки по Y
    if ((!this.limitBoolRight || (this.node.offsetLeft > pageX)) && pageX > 0) {
      // Если (не заходили справа за пределы ИЛИ позиция блока слева больше позиция мышки по X) И позиция мышки по X больше 0
      this.node.style.left = pageX + 'px';
    }
    if ((!this.limitBoolBottom || (this.node.offsetTop > pageY)) && pageY > 0) {
      // Если (не заходили снизу за пределы ИЛИ позиция блока сверху больше позиция мышки по Y) И позиция мышки по Y больше 0
      this.node.style.top = pageY + 'px';
    }
  }

  doNotGoBeyond() {
    this.limitBoolRight = false;
    this.limitBoolBottom = false;
    const bottom = this.heightGame - this.node.offsetTop - this.node.offsetHeight;
    const right = this.widthGame - this.node.offsetLeft - this.node.offsetWidth;
    if (right <= 0) { // Если зашли справа за пределы
      this.limitBoolRight = true;
      this.node.style.left = this.widthGame - this.node.offsetWidth + 'px';
    }
    if (bottom <= 0) { // Если зашли снизу за пределы
      this.limitBoolBottom = true;
      this.node.style.top = this.heightGame - this.node.offsetHeight + 'px';
    }
  }

  onMouseMove(event) {
    this.doNotGoBeyond();
    const page = {
      x: event.pageX,
      y: event.pageY
    };
    this.move(page);
  }
}

addBlocks();

function addBlocks() {

  const width = '100px';
  const height = '100px';

  const block1 = new BlockInGame(undefined, {
    backgroundColor: 'red',
    width: width,
    height: height,
    left: '50px',
    top: '50px'
  });


  const block2 = new BlockInGame(undefined, {
    backgroundColor: 'yellow',
    width: width,
    height: height,
    left: '250px',
    top: '50px'
  });

  const block3 = new BlockInGame(undefined, {
    backgroundColor: 'black',
    width: width,
    height: height,
    left: '450px',
    top: '50px'
  });
}
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');
*,
*::before,
*::after {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: 'Poppins', sans-serif;
}

section {
  background-color: #00cbff;
  min-height: 100vh;
}

.game {
  position: relative;
  overflow: hidden;
}

.move__block {
  -webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.75);
  -moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.75);
  box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.75);
}
<section class="game">

</section>

→ Ссылка