I made a small(ish) image editor for a client that is supposed to be able to do the following:initially scale image to preset size to avoid tiny/huge imagesinitially center image in canvasresize uploaded image (while always maintaining original aspect ratio)prevent image from going outside the canvas boundsprevent image from getting too smallIt's a little bit glitchy, but for the most part it works. I think with cleaning up the structure/program flow it will work significantly better even though it's only ~300 lines of ES6 JavaScript. Thoughts?I think my biggest gripe is that it is hard to read, although the code is simple and has well-named variables. The image properties are handled across multiple different methods at different times and this has a lot of room for failure. I was thinking I need to redesign it so it has a size updater that maintains proportions and more convenience methods instead of manually performing so much math that has large possible error margins.Here is a working JSFiddle:'use strict';const canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');ctx.lineWidth = 1;ctx.strokeStyle = 'black';// cursors for different resize handle indicesconst cursors = ['nw-resize', 'ne-resize', 'se-resize', 'sw-resize'];// for use in calculating mouse x/y positionconst canvasOffset = document.getElementById('canvas').getBoundingClientRect();const offsetX = canvasOffset.left;const offsetY = canvasOffset.top;// starting position for the mouse x/y positionlet startX;let startY;// resize all images to start at this size while maintaining aspect ratioconst INITIAL_IMG_SIZE = 50;// minimum resize value allowed for either height or widthconst MIN_IMG_SIZE = 25;// current mouse x/y valueslet mouseX;let mouseY;const RESIZE_HANDLE_SIZE = 8;const RESIZE_HANDLE_SQUARED = RESIZE_HANDLE_SIZE * RESIZE_HANDLE_SIZE;// keeps track of which resize handle is currently being usedlet resizeHandleIndex;// furthest top, right, bottom, and left points of the image respectivelylet imageY;let imageRight;let imageBottom;let imageX;let imageWidth;let imageHeight;let draggingImage = false;let imageAspectRatio;const img = new Image();img.src = 'http://www.vertical-corp.com/assets/images/vertical_logo.jpg';function drawResizeHandles(x, y) {  const left = x - (RESIZE_HANDLE_SIZE / 2);  const top = y - (RESIZE_HANDLE_SIZE / 2);  const handle = new Path2D();  handle.rect(left, top, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE);  ctx.stroke(handle);}function draw() {  ctx.clearRect(0, 0, canvas.width, canvas.height);  ctx.drawImage(img, 0, 0, img.width, img.height, imageX, imageY, imageWidth, imageHeight);  // draw top left, top right, bottom right, and bottom left resize handles respectively  drawResizeHandles(imageX, imageY);  drawResizeHandles(imageRight, imageY);  drawResizeHandles(imageRight, imageBottom);  drawResizeHandles(imageX, imageBottom);  // draw image border  const rectangle = new Path2D();  rectangle.rect(imageX, imageY, imageWidth, imageHeight);  ctx.stroke(rectangle);}img.onload = () => {  imageWidth = img.width;  imageHeight = img.height;  imageAspectRatio = imageWidth / imageHeight;  // resize image to default initial size while keeping original aspect ratio  if (imageWidth > imageHeight) {    imageHeight = INITIAL_IMG_SIZE;    imageWidth = imageHeight * imageAspectRatio;  } else {    imageWidth = INITIAL_IMG_SIZE;    imageHeight = imageWidth / imageAspectRatio;  }  // center image in canvas  imageX = (canvas.width / 2) - (imageWidth / 2);  imageY = (canvas.height / 2) - (imageHeight / 2);  imageRight = imageX + imageWidth;  imageBottom = imageY + imageHeight;  draw();};function resizeHandleHitTest(x, y) {  let dx;  let dy;  // top-left  dx = x - imageX;  dy = y - imageY;  if (dx * dx + dy * dy <= RESIZE_HANDLE_SQUARED) {    return 0;  }  // top-right  dx = x - imageRight;  dy = y - imageY;  if (dx * dx + dy * dy <= RESIZE_HANDLE_SQUARED) {    return 1;  }  // bottom-right  dx = x - imageRight;  dy = y - imageBottom;  if (dx * dx + dy * dy <= RESIZE_HANDLE_SQUARED) {    return 2;  }  // bottom-left  dx = x - imageX;  dy = y - imageBottom;  if (dx * dx + dy * dy <= RESIZE_HANDLE_SQUARED) {    return 3;  }  // not a resize handle  return -1;}function imageHitTest(x, y) {  return (x > imageX && x < imageX + imageWidth && y > imageY && y < imageY + imageHeight);}function imageIsOutOfBounds() {  // outside the left, top, right, and bottom of canvas respectively  return (imageX < 0) || (imageY < 0) || ((imageX + imageWidth) > canvas.width) || ((imageY + imageHeight) > canvas.height);}canvas.onmousedown = e => {  startX = parseInt(e.clientX - offsetX, 10);  startY = parseInt(e.clientY - offsetY, 10);  resizeHandleIndex = resizeHandleHitTest(startX, startY);  draggingImage = resizeHandleIndex < 0 && imageHitTest(startX, startY);};canvas.onmouseup = () => {  resizeHandleIndex = -1;  draggingImage = false;};canvas.onmouseout = () => {  canvas.onmouseup();};function protectImageFrame() {  const minSizeReached = imageWidth <= MIN_IMG_SIZE || imageHeight <= MIN_IMG_SIZE;  const maxSizeReached = imageWidth >= canvas.width || imageHeight >= canvas.height;    if (!minSizeReached && !maxSizeReached) {   return;  }  // prevent image from becoming unreasonably small  if (minSizeReached) {    if (imageWidth >= imageHeight) {      imageHeight = MIN_IMG_SIZE;      imageWidth = imageHeight * imageAspectRatio;    } else {      imageWidth = MIN_IMG_SIZE;      imageHeight = imageWidth / imageAspectRatio;    }  }    imageX = Math.max(imageX, 0);  imageY = Math.max(imageY, 0);  imageRight = Math.min(imageRight, canvas.width);  imageBottom = Math.min(imageBottom, canvas.height);    const desiredWidth = imageRight - imageX;  const desiredHeight = imageBottom - imageY;  // if we've reached the max size, verify we're only attempting to shrink  let canUpdateSize;     if (maxSizeReached) {    if (imageWidth >= canvas.width) {      canUpdateSize = (desiredWidth <= canvas.width);    } else {      canUpdateSize = (desiredHeight <= canvas.height);    }  }  if (canUpdateSize) {    if (imageWidth > imageHeight) {      imageWidth = desiredWidth;      let possibleHeight = imageWidth / imageAspectRatio;      imageHeight = imageWidth / imageAspectRatio;    } else {      imageHeight = desiredHeight;      imageWidth = imageHeight * imageAspectRatio;    }  }}function canUpdateSize(desiredWidth, desiredHeight) {  const minSizeReached = imageWidth <= MIN_IMG_SIZE || imageHeight <= MIN_IMG_SIZE; // we don't want to move images that are locked for resize  if (minSizeReached) {    if (imageWidth >= imageHeight) {      return desiredHeight >= MIN_IMG_SIZE;    } else {      return desiredWidth >= MIN_IMG_SIZE;    }  } else {    return true;  }}canvas.onmousemove = e => {  mouseX = parseInt(e.clientX - offsetX, 10);  mouseY = parseInt(e.clientY - offsetY, 10);  const cursorOverResizeHandle = resizeHandleHitTest(mouseX, mouseY);  if (cursorOverResizeHandle < 0) {    document.body.style.cursor = 'default';    if (imageHitTest(mouseX, mouseY)) {      document.body.style.cursor = 'move';    }  } else {    document.body.style.cursor = cursors[cursorOverResizeHandle];  }  // dont resize or move if the image is out of bounds  if (!imageIsOutOfBounds() && resizeHandleIndex > -1) {    // resize the image    let desiredWidth;    let desiredHeight;    document.body.style.cursor = cursors[resizeHandleIndex];    switch (resizeHandleIndex) {      case 0:        // top-left        desiredWidth = imageRight - mouseX;        desiredHeight = desiredWidth / imageAspectRatio;        if (canUpdateSize(desiredWidth, desiredHeight)) {          imageX = mouseX;          imageWidth = desiredWidth;          imageY = mouseY;          imageHeight = desiredHeight;          // to keep the bottom position immobile          imageY -= imageHeight - (imageBottom - imageY);        }        break;      case 1:        // top-right        desiredWidth = mouseX - imageX;        desiredHeight = imageWidth / imageAspectRatio;        if (canUpdateSize(desiredWidth, desiredHeight)) {          imageY = mouseY;          imageRight = imageX + imageWidth;          imageWidth = desiredWidth;          imageHeight = desiredHeight;          // to keep the bottom position immobile          imageY -= imageHeight - (imageBottom - imageY);        }        break;      case 2:        // bottom-right        imageBottom = imageY + imageHeight;        imageRight = imageX + imageWidth;        imageWidth = mouseX - imageX;        imageHeight = imageWidth / imageAspectRatio;        break;      case 3:        // bottom-left        imageBottom = imageY + imageHeight;        imageX = mouseX;        imageWidth = imageRight - mouseX;        imageHeight = imageWidth / imageAspectRatio;        break;      default:        document.body.style.cursor = 'default';        break;    }    protectImageFrame();  } else if (draggingImage) {    // move the image by the x/y deltas    const dx = mouseX - startX;    const dy = mouseY - startY;    const desiredX = imageX + dx;    const desiredY = imageY + dy;    const desiredRight = desiredX + imageWidth;    const desiredBottom = desiredY + imageHeight;    if (desiredX >= 0 && desiredRight <= canvas.width) {      imageX = desiredX;      imageRight = desiredRight;    }    if (desiredY >= 0 && desiredBottom <= canvas.height) {      imageY = desiredY;      imageBottom = desiredBottom;    }    // reset startX/Y    startX = mouseX;    startY = mouseY;  }  draw();};#canvas {  background: white;  border: 1px solid black;}<canvas id=canvas width=350 height=350></canvas>
Tiny Image Editor
javascript;ecmascript 6;canvas