JSFiddle - React, Tailwind, and code Playground

by joshmoto

HTML

<div class="area">
  <img src="https://dragonballsuper-france.fr/wp-content/uploads/2017/10/DIHQF2OXoAAJi4n-660x330.jpg" alt="" />
  <div class="rect">
    <div class="exclusion-x"></div>
    <div class="exclusion-y"></div>
  </div>
</div>

CSS

/* area div */
.area {
  overflow: hidden;
  position: relative;
  margin-bottom: 8px;
}

/* area div hover cursor */
.area:hover {
  cursor: crosshair;
}

/* img tag in area div */
.area IMG {
  display: block;
  max-width: 100%;
  pointer-events: none;
  user-select: none;
}

/* rectangle div in area div */
.area .rect {
  opacity: 0;
  transition: all 0s ease;
  position: absolute;
  border: 1px solid red;
  z-index: 1;
}

/* rectangle div css when in draw or drawn mode */
.area.draw .rect,
.area.drawn .rect {
  opacity: 1;
}

/* below css is for fun rendering outer exclusion area of drawn rectangle div with a opaque overlay */

/* rectange exclusing pseudo elems base css */
.area.drawn .rect .exclusion-x::before,
.area.drawn .rect .exclusion-x::after,
.area.drawn .rect .exclusion-y::before,
.area.drawn .rect .exclusion-y::after {
  position: absolute;
  content: '';
  display: block;
  background: #000;
  opacity: .75;
  z-index: -1;
  pointer-events: none;
  user-select: none;
}

/* rectange outer opaque x above css */
.area.drawn .rect .exclusion-x::before {
  bottom: calc(100% + 1px);
  left: 50%;
  transform: translateX(-50%);
  height: 200vh;
  width: 200vw;
}

/* rectange outer opaque x below css */
.area.drawn .rect .exclusion-x::after {
  top: calc(100% + 1px);
  left: 50%;
  transform: translateX(-50%);
  height: 200vh;
  width: 200vw;
}

/* rectange outer opaque y left css */
.area.drawn .rect .exclusion-y::before {
  right: calc(100% + 1px);
  top: -1px;
  bottom: -1px;
  width: 200vw;
}

/* rectange outer opaque y right css */
.area.drawn .rect .exclusion-y::after {
  left: calc(100% + 1px);
  top: -1px;
  bottom: -1px;
  width: 200vw;
}

JavaScript

// our updatable variable objects to use globally
let img = {};
let position = {};

// image matrix function to update img object variable
function imgMatrix() {

  // our image object inside area
  let $img = $('IMG', '.area');

  // offset data of image
  let offset = $img.offset();

  // add/update object key data
  img.width = $img.outerWidth();
  img.height = $img.outerHeight();
  img.offsetX = offset.left - $(document).scrollLeft();
  img.offsetY = offset.top - $(document).scrollTop();

}

// position matrix function to update position object variable
function positionMatrix(e, mousedown = false) {

  // if mousedown param is true... for use in 
  if (mousedown) {

    // set the top/left position object data with percentage position
    position.top = (100 / img.height) * (e.pageY - img.offsetY);
    position.left = (100 / img.width) * (e.pageX - img.offsetX);

  }

  // set the right/bottom position object data with percentage position
  position.right = 100 - ((100 / img.width) * (e.pageX - img.offsetX));
  position.bottom = 100 - ((100 / img.height) * (e.pageY - img.offsetY));

}

// mouse move event function in area div
$(document).on('mousemove', '.area', function(e) {

  // / update img object variable data upon this mousemove event
  imgMatrix();

  // if this area has draw class
  if ($(this).hasClass('draw')) {

    // update position object variable data passing current event data
    positionMatrix(e);

    // if image x cursor drag position percent is negative to mousedown x position
    if ((100 - position.bottom) < position.top) {

      // update rectange x negative positions css
      $('.rect', this).css({
        top: (100 - position.bottom) + '%',
        bottom: (100 - position.top) + '%'
      });

      // else if image x cursor drag position percent is positive to mousedown x position
    } else {

      // update rectange x positive positions css
      $('.rect', this).css({
        bottom: position.bottom + '%',
        top:...