JSFiddle - React, Tailwind, and code Playground

by TeWu

HTML

<div>Move the cursor inside the red rectangle, try it! :)</div>
  <div>
    <canvas id="bg-canvas" style="position: absolute; top: 10; left: 10;"></canvas>
    <canvas id="point-canvas" style="position: absolute; top: 10; left: 10;"></canvas>
  </div>

JavaScript

/////// INPUTS /////////////////
const w = 220;
const h = 115;

const p1 = {x:-79, y:80 };
const p2 = {x:9,   y:-96};
const p3 = {x:55,  y:-72};
const p4 = {x:-34, y:105};
////////////////////////////////


// Creates transformation matrix to transform
// from rectangle somewhere in 2D space with coordinates p0, px, pi, py
// to rectangle with coordinates (x=0, y=0), (x=w, y=0), (x=w, y=h), (x=0, y=h).
// Note that: p0 is mapped to (x=0, y=0)
//            px is mapped to (x=w, y=0)
//            py is mapped to (x=0, y=h)
const createTransformationMatrix = function(p0, px, py, w, h) {
  // Translate px and py by p0 - pxt and pyt are px and py vectors in coordinate system in which p0 is at the origin
  const pxt = {
    x: px.x - p0.x,
    y: px.y - p0.y,
  };
  const pyt = {
    x: py.x - p0.x,
    y: py.y - p0.y,
  };

  // Create transformation matrix matrix, which is inverse of transformation matrix that:
  //   1. Transforms (x=0, y=0) to (x=p0.x,             y=p0.y)
  //   2. Transforms (x=1, y=0) to (x=p0.x + pxt.x / w, y=p0.y + pxt.y / w)
  //   3. Transforms (x=0, y=1) to (x=p0.x + pyt.x / h, y=p0.y + pyt.y / h)
  return Matrix.invert3([
    [pxt.x / w, pyt.x / h, p0.x],
    [pxt.y / w, pyt.y / h, p0.y],
    [0        , 0        , 1   ],
  ]);
};

const transform = function(point, transformationMatrix) {
  // Convert point to homogeneous coordinates
  const inputVector = [
    [point.x],
    [point.y],
    [1],
  ];
  // Transform inputVector
  const outputVector = Matrix.multiply(transformationMatrix, inputVector);
  // Convert outputVector back to cartesian coordinates and return
  return {
    x: outputVector[0][0] / outputVector[2][0],
    y: outputVector[1][0] / outputVector[2][0],
  };
};


function docReady(fn) {
  if (document.readyState === "complete" || document.readyState === "interactive") setTimeout(fn, 1);
  else document.addEventListener("DOMContentLoaded", fn);
}
docReady(function() {
  const canvas =...