JQuery - Drawing rectangle on image

Viewed 2144

I am trying to draw a red rectangle on image but somehow my rectangle is NOT starting from coordinates where I click with my mouse. Here is the code:

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

let img = new Image();
let fileName = "";

let rect = {};
let drag = false;
  // Add image to canvas
reader.addEventListener(
    "load",
    () => {
      // Create image
      img = new Image();
      // Set image src
      img.src = reader.result;
      // On image load add to canvas
      img.onload = function() {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0, img.width, img.height);
        canvas.removeAttribute("data-caman-id");
      };
      canvas.addEventListener('mousedown', mouseDown, false);
      canvas.addEventListener('mouseup', mouseUp, false);
      canvas.addEventListener('mousemove', mouseMove, false);
    },
    false
  );
});


function mouseDown(e) {
    rect.startX = e.pageX - this.offsetLeft;
    rect.startY = e.pageY - this.offsetTop;
    drag = true;
}

function mouseUp() { drag = false; }

function mouseMove(e) {
    if (drag) {
        ctx.clearRect(0, 0, 500, 500);
        ctx.drawImage(img, 0, 0);
        rect.w = (e.pageX - this.offsetLeft) - rect.startX;
        rect.h = (e.pageY - this.offsetTop) - rect.startY;
        ctx.strokeStyle = 'red';
        ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
        console.log("rect.startX: " + rect.startX + " - rect.startY: " +rect.startY)
    }
}

And here is what I get in return:

enter image description here

I cannot show my mouse position when I take screenshot so I've labeled it on the image. Basically, I start from top left corner and rectangle seems to start drawing itself from bottom right corner. What am I doing wrong?

1 Answers

OK my answer is more related to the (original) question title and a little bit of jQuery fun.

Your (original) question code involves canvas and no jQuery code, just javascript.

This answer is more just a primitive jQuery approach.

Here is a very rough concept below using jQuery .on events for handling...

  • mousedown event to start drawing the rectangle
  • resize event for updating image size and offsets
  • mousemove event for updating rectangle draw positions
  • mouseup event for setting rectangle in set position

All of the above .on events (excluding resize event) run in document selector .area, which has a child img element in this example below.

Please note I have not coded this example for multiple usages of .area div, you will need go even deeper with the script logic in my example to accommodate multiple usages of .area div.

My script example code below may inspire you to get to where you want, may not.

The main key thing I am doing is converting pixel cursor positions within the image into a percentage integer. Using percentages as xy positions means the rendered result will be the same if the window or .area is resized responsively (with no javascript).

Here is a jQuery and CSS fun example below.

jsFiddle version: https://jsfiddle.net/joshmoto/w5bcx9o8/2/

See comments in code to understand what is happening...

// 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 - $(document).scrollTop()) - img.offsetY);
    position.left = (100 / img.width) * ( (e.pageX - $(document).scrollLeft()) - img.offsetX);

  }

  // set the right/bottom position object data with percentage position
  position.right = 100 - ((100 / img.width) * ((e.pageX - $(document).scrollLeft()) - img.offsetX));
  position.bottom = 100 - ((100 / img.height) * ((e.pageY - $(document).scrollTop()) - 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: position.top + '%',
      });

    }

    // if image y cursor drag position percent is negative to mousedown y position
    if ((100 - position.right) < position.left) {

      // update rectange y negative positions css
      $('.rect', this).css({
        left: (100 - position.right) + '%',
        right: (100 - position.left) + '%'
      });

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

      // update rectange y positive positions css
      $('.rect', this).css({
        right: position.right + '%',
        left: position.left + '%'
      });

    }

  }

});

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

  // remove the drawn class
  $(this).removeClass('drawn');

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

  // update position object variable data passing current event data and mousedown param as true 
  positionMatrix(e, true);

  // update rectange xy positions css
  $('.rect', this).css({
    left: position.left + '%',
    top: position.top + '%',
    right: position.right + '%',
    bottom: position.bottom + '%'
  });

  // add draw class to area div to reveal rectangle
  $(this).addClass('draw');

});

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

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

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

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

    // math trunc on position values if x and y values are equal, basically no drawn rectangle on mouseup event
    if ((Math.trunc(position.left) === Math.trunc(100 - position.right)) && (Math.trunc(position.top) === Math.trunc(100 - position.bottom))) {
      
      // remove draw and drawn classes
      $(this).removeClass('draw drawn');

    // else if x and y values are not equal, basically a rectange has been drawn
    } else {

      // add drawn class and remove draw class
      $(this).addClass('drawn').removeClass('draw');

    }

  }

});

// on window resize function
$(window).on('resize', function(e) {
  
  // update img object variable data upon this window resize event
  imgMatrix();

});
/* 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;
  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;
}
<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>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Related