Pinch zoom is not working well... It's also translating the image

253 views Asked by At

I'm building an image color picker with pinch zoom and pan with JS Vanilla. I'm almost good but I still have some issues with :

  1. The pinch zoom behaviour : When I pinch zoom, it's also translating. I would like to zoom on the same z axis.

  2. The bahaviour on my mobile is completely different than on Chrome dev tool Mobile emulator. For example, my pan bounds limits are not taking into account on mobile.

Here is what I've done :

I've created a canva in which I draw an image. This image is scaled according to a zoom listened with touchstart, touchend and touchmove.

I've created a other canva for the picker in which I draw the zoomed image just after having draw the canva above.

My HTML code :

    <div class="canva">
      <%= cl_image_tag "#{@sketch.photo.key}", id: "my-image"  %>
      <div id="image-picker_image-container">
        <div id="image-picker_image-element">
          <div id="image-picker_cursors">
            <div class="image-picker_cursor is-active" id="cursor-picker">
              <canvas id="cursorCanva" width="130" height="130"></canvas>
            </div>
          </div>
          <canvas id="image-picker_image_canvas"></canvas>
        </div>
      </div>
    </div>

My JS code :

    // Main canva
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.src = document.getElementById("my-image").getAttribute("src");
    const canvas = document.getElementById("image-picker_image_canvas");
    const ctx = canvas.getContext("2d");

    // Zoom canva
    const imgZoom = new Image();
    imgZoom.crossOrigin = "anonymous";
    imgZoom.src = document.getElementById("my-image").getAttribute("src");
    const zoomCanva = document.getElementById("cursorCanva");
    const zoomLevel = 30;

    // Picker behaviour variables
    const newCursor = document.getElementById("cursor-picker");
    var x = 0,
      y = 0,
      mousedown = false;

    // Pinch Zoom variables
    var image_x = 0,
      image_y = 0;
    var zoom = 0.5;
    var mouse_x = 0,
      mouse_y = 0,
      finger_dist = 0;

// We define the size of the main canva
    canvas.width = window.screen.width;
    canvas.height = window.screen.height * 0.6;

    // We define the size of the zoom canva
    const zoomCanva_w = zoomCanva.width;
    const zoomCanva_h = zoomCanva.height;
    console.log(zoomCanva_w);

    const canvas_w = canvas.width,
      canvas_h = canvas.height;

    // Functions >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    // Pick function to get color of the hovered pixel
    function pick(event, destination) {
      const bounding = canvas.getBoundingClientRect();
      let x = 0;
      let y = 0;
      if (touchMode) {
        x = event.touches[0].pageX - bounding.left;
        y = event.touches[0].pageY - bounding.top;
      } else {
        x = event.clientX - bounding.left;
        y = event.clientY - bounding.top;
      }
      const pixel = ctx.getImageData(x, y, 1, 1);
      const data = pixel.data;

      const rgba = `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${data[3] / 255})`;

      destination.style.background = rgba;
      destination.style.borderColor = rgba;
      // destination.textContent = `(${x}, ${y})`;

      return rgba;
    }

    // Get the Hex code of a color
    function hexColour(c) {
      let hex = Math.abs(c).toString(16);
      return hex.length == 1 ? "0" + hex : hex;
    }

    // Convert RGB code to Hex code
    function rgbToHex(r, g, b) {
      return "#" + hexColour(r) + hexColour(g) + hexColour(b);
    }

    function update_canvas() {
      const mainCanvasCTX = canvas.getContext("2d");
      // console.log(zoom);

      const limitXRight = img.width - (canvas_w * zoom) / 2 - zoomLevel / 2;
      const limitXLeft = - zoomLevel / 2 - (canvas_w * zoom) / 2;
      const limitYTop = - zoomLevel / 2 - (canvas_h * zoom) / 2 ;
      const limitYBottom = img.height - zoomLevel / 2 - (canvas_h * zoom) / 2;

      // Keep picture in bounds

      // Limit X Right
      if (image_x > limitXRight) image_x = limitXRight;

      // Limit Y Bottom
      if (image_y > limitYBottom) image_y = limitYBottom;

      // Limit X Left
      if (image_x < limitXLeft) image_x = limitXLeft;

      // Limit Y Top
      if (image_y < limitYTop) image_y = limitYTop;

      console.log("x="+image_x);
      console.log("y="+image_y);

      // Draw the scaled image onto the canvas
      mainCanvasCTX.clearRect(0, 0, canvas_w, canvas_h);
      mainCanvasCTX.drawImage(
        img,
        image_x,
        image_y,
        canvas_w * zoom,
        canvas_h * zoom,
        0,
        0,
        canvas_w,
        canvas_h
      );

      const pixelatedZoomCtx = document
        .getElementById("cursorCanva")
        .getContext("2d");
      pixelatedZoomCtx.imageSmoothingEnabled = false;
      pixelatedZoomCtx.mozImageSmoothingEnabled = false;
      pixelatedZoomCtx.webkitImageSmoothingEnabled = false;
      pixelatedZoomCtx.msImageSmoothingEnabled = false;

      const zoomCursor = (ctx, x, y) => {
        // console.log(x);
        // console.log(
        //   Math.min(Math.max(0, x - zoomLevel / 2), img.width - zoomLevel)
        // );
        ctx.clearRect(0, 0, zoomCanva_w, zoomCanva_h);
        ctx.drawImage(
          canvas,
          Math.min(Math.max(0, x), img.width),
          Math.min(Math.max(0, y), img.height),
          zoomLevel,
          zoomLevel,
          0,
          0,
          pixelatedZoomCtx.canvas.width,
          pixelatedZoomCtx.canvas.height
        );

        // Adding a circle and a cross to select color more precisely
        const rayon = 30;
        const centerX = pixelatedZoomCtx.canvas.width / 2;
        const centerY = pixelatedZoomCtx.canvas.height / 2;
        const crossLength = 5;
        ctx.beginPath();
        ctx.lineWidth = 5;
        ctx.strokeStyle = "white";
        ctx.arc(centerX, centerY, rayon, 0, 2 * Math.PI, false);
        ctx.moveTo(centerX, centerY - crossLength);
        ctx.lineWidth = 3;
        ctx.lineTo(centerX, centerY + crossLength);
        ctx.moveTo(centerX - crossLength, centerY);
        ctx.lineTo(centerX + crossLength, centerY);
        ctx.stroke();
        ctx.strokeStyle = "white";

        const pixel = ctx.getImageData(60, 60, 1, 1); // Allow to get color when we move and zoom canvas
        const data = pixel.data;

        const rgba = `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${
          data[3] / 255
        })`;

        const newCursor = document.getElementById("cursor-picker");
        newCursor.children[0].style.background = rgba;
        newCursor.children[0].style.borderColor = rgba;
        colorChecker.style.background = rgba;
        colorCheckerHexCode.textContent = rgbToHex(
          data[0],
          data[1],
          data[2]
        ).toUpperCase();
      };

      zoomCursor(pixelatedZoomCtx, canvas_w / 2, canvas_h / 2); // Initialize cursor zoom content
    }

    function reset_settings() {
      if (img.height > img.width) {
        zoom = img.height / canvas_h;
      } else {
        zoom = img.width / canvas_w;
      }
      image_x = 0;
      image_y = -(img.height - zoomLevel / 2 - (canvas_h * zoom) / 2);
      newCursor.style.top = canvas_h / 2 + "px";
      newCursor.style.left = canvas_w / 2 + "px";
      update_canvas(); // Draw the image in its new position
    }

    function get_distance(e) {
      var diffX = e.touches[0].clientX - e.touches[1].clientX;
      var diffY = e.touches[0].clientY - e.touches[1].clientY;
      return Math.sqrt(diffX * diffX + diffY * diffY); // Pythagorean theorem
    }

    img.addEventListener(
      "load",
      function () {
        reset_settings();
      },
      false
    ); // Reset (x,y,zoom) when new image loads

// We add a touchstart listener on canvas to get fingers positions and calculate finger distance
    canvas.addEventListener(
      "touchstart",
      function (e) {
        if (e.touches.length > 1) {
          // if multiple touches (pinch zooming)
          finger_dist = get_distance(e); // Save current finger distance
        } // Else just moving around
        mouse_x = e.touches[0].clientX; // Save finger position
        mouse_y = e.touches[0].clientY; //
      },
      false
    );

    // We add a touchmove listener on canvas to zoom canvas according to old and new distance of the fingers
    canvas.addEventListener(
      "touchmove",
      function (e) {
        e.preventDefault(); // Stop the window from moving
        if (e.touches.length > 1) {
          // If pinch-zooming
          var new_finger_dist = get_distance(e); // Get current distance between fingers
          zoom = zoom * Math.abs(finger_dist / new_finger_dist); // Zoom is proportional to change
          let zoomMin = 0;
          let zoomMax = 0;
          if (img.height > img.width) {
            zoomMin = img.height / canvas_h;
          } else {
            zoomMin = img.width / canvas_w;
          }
          zoomMax = zoomMin / 15; // We define maximum zoom relatively to minZoom
          if (zoom > zoomMin) zoom = zoomMin;
          if (zoom < zoomMax) zoom = zoomMax;
          finger_dist = new_finger_dist; // Save current distance for next time
        } else {
          // Else just moving around
          const decalX = zoom * (mouse_x - e.touches[0].clientX)
          const decalY = zoom * (mouse_y - e.touches[0].clientY)
          image_x = image_x + decalX; // Move the image
          image_y = image_y + decalY; //
          console.log("image_x : " + image_x);
          console.log("mouse_x : " + mouse_x);
          console.log("touchX : " + e.touches[0].clientX);
          mouse_x = e.touches[0].clientX; // Save finger position for next time
          mouse_y = e.touches[0].clientY; //
        }
        update_canvas(); // draw the new position
      },
      false
    );

    // We add a touchend listener on canvas to get coordinates of fingers when touch end
    canvas.addEventListener(
      "touchend",
      function (e) {
        mouse_x = e.touches[0].clientX;
        mouse_y = e.touches[0].clientY; // could be down to 1 finger, back to moving image
      },
      false
    );
0

There are 0 answers