0

I wrote some code to zoom in my image, but when I scroll at the very beginning this picture jumps a little. How to fix the problem?

Full page view.

Editor view.

HTML

<canvas id="canvas"></canvas>

JS

function draw(scroll) {
  scroll = (window.scrollY || window.pageYOffset) / (document.body.clientHeight - window.innerHeight) * 3000;
  canvas.setAttribute('width', window.innerWidth);
  canvas.setAttribute('height', window.innerHeight);

//The main formula that draws and zooms the picture

  drawImageProp(ctx, forest, 0, (-scroll * 3.9) / 4, canvas.width, canvas.height + (scroll * 3.9) / 2);
}
Alex Nikolsky
  • 2,087
  • 6
  • 21
  • 36

1 Answers1

1

Not a bug fix

I had a look at the Codepen example and it does jump at the top (sometimes). I have a fix for you but I did not have the time to locate the source of your code problem. I did notice that the jump involved a aspect change so it must be in the scaling that your error is. (look out for negatives)

GPU is a better clipper

Also your code is actually doing unnecessary work, because you are calculating the image clipping region. Canvas context does the clipping for you and is especially good at clipping images. Even though you provide the clip area the image will still go through clip as that is part of the render pipeline. The only time you should be concerned about the clipped display of an image is whether or not any part of the image is visible so that you don't send a draw call, and it only really matters if you are pushing the image render count (ie game sprite counts 500+)

Code example

Anyway I digress. Below is my code. You can add the checks and balances. (argument vetting, scaling max min, etc).

Calling function.

  // get a normalised scale 0-1 from the scroll postion
  var scale = (window.scrollY || window.pageYOffset) / (document.body.clientHeight - window.innerHeight);
  // call the draw function 
  // scale 0-1 where 0 is min scale and 1 is max scale (min max determined in function
  // X and y offset are clamped but are ranged
  // 0 - image.width and 0 - image.height
  // where 0,0 shows top left and width,height show bottom right
  drawImage(ctx, forest, scale, xOffset, yOffset);

The function.

The comments should cover what you need to know. You will notice that all I am concerned with is how big the image should be and where the top left corner will be. The GPU will do the clipping for you, and will not cost you processing time (even for unaccelerated displays). I personally like to work with normalised values 0-1, it is a little extra work but my brain likes the simplicity, it also reduces the need for magic numbers (magics number are a sign that code is not adaptable) . Function will work for any size display and any size image. Oh and I like divide rather than multiply, (a bad coding habit that comes from a good math habit) replacing the / 2 and needed brackets with * 0.5 will make it more readable.

function drawImage(ctx, img, scale, x, y){
  const MAX_SCALE = 4;
  const MIN_SCALE = 1;
  var w = canvas.width; // set vars just for source clarity
  var h = canvas.height;

  var iw = img.width;
  var ih = img.height;

  var fit = Math.max(w / iw, h / ih); // get the scale to fill the avalible display area

  // Scale is a normalised value from 0-1 as input arg Convert to range
  scale = (MAX_SCALE - MIN_SCALE) * scale + MIN_SCALE; 

  var idw = iw * fit * scale; // get image total display size;
  var idh = ih * fit * scale;

  x /= iw; // normalise offsets
  y /= ih; //

  x = - (idw - w) * x;  // transform offsets to display coords
  y = - (idh - h) * y;

  x = Math.min( 0, Math.max( - (idw - w), x) ); // clamp image to display area
  y = Math.min( 0, Math.max( - (idh - h), y) ); 

  // use set transform to scale and translate
  ctx.setTransform(scale, 0, 0, scale, idw / 2 + x, idh / 2 + y);  

  // display the image to fit;
  ctx.drawImage(img, ( - iw / 2 ) * fit, (- ih / 2 ) * fit);

  // restore transform.
  ctx.setTransform(1, 0, 0, 1, 0, 0)
}

Sorry I did not solve the problem directly, but hopefully this will help you redesign your approch.

I recently added a similar answer involving zooming and panning (and rotation) with the mouse which you may be interested in How to pan the canvas? Its a bit messy still "note to self (my clean it up)" and has no bounds clamping. But shows how to set a zoom origin, and convert from screen space to world space. (find where a screen pixel is on a pan/scale/rotated display).

Good luck with your project.

Community
  • 1
  • 1
Blindman67
  • 51,134
  • 11
  • 73
  • 136