Maintaining Object Position in 2D Transforms

I am trying to create multiple perspective-transformed rectangles in the lower right corner of a canvas by using ctx.transform:

ctx.transform(1, 0, -1, 1, 10, 10)
.

My goal now is to adjust the size of the drawing based on a variable scale=n, while keeping the position centered. However, when I move the slider, it changes the position of the shapes. How can I prevent this from happening?

let canvas = document.getElementById("canvas")
canvas.width = canvas.height = 200;
$(canvas).appendTo(document.body)
let ctx = canvas.getContext("2d")

let update = function(input) {
  let scale = input.value;
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  ctx.transform(1, 0, -1, 1, 10, 10)
  for (let i = 0; i < 4; i++) {
    ctx.fillStyle = (i === 2) ? "#3b2a19" : "#957e67"
    let layer = {
      x: canvas.width + scale * 7 - i * scale,
      y: canvas.height - scale * 5 - i * scale,
      width: scale * 3,
      height: scale * 1.5
    }
    ctx.fillRect(layer.x, layer.y, layer.width, layer.height)
    ctx.strokeRect(layer.x, layer.y, layer.width, layer.height)
  }
  ctx.resetTransform();
}

$("input").trigger("input")
#canvas {
  border: 2px solid red
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>

<input oninput="update(this)" type="range" min="1" max="20" />

Answer №1

It is suggested to manually draw the shapes instead of using the transform function.

Refer to the following example code:

let canvas = document.getElementById("canvas")
canvas.width = canvas.height = 200;
$(canvas).appendTo(document.body)
let ctx = canvas.getContext("2d")

function shape(x,y,s) {
  var f = s/18
  ctx.moveTo(x-10*f, y-60*f);
  ctx.lineTo(x-110*f, y-60*f);
  ctx.lineTo(x-160*f, y-10*f);
  ctx.lineTo(x-60*f, y-10*f);
  ctx.lineTo(x-10*f, y-60*f);
  ctx.fill();
  ctx.stroke();
}

let update = function(input) {
  let scale = input.value;
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  ctx.lineWidth = Math.min(scale/2, 2);
  for (let i = 0; i < 4; i++) {
    ctx.beginPath();
    ctx.fillStyle = (i === 2) ? "#3b2a19" : "#957e67"
    shape(canvas.width, canvas.height -i * scale * 1.5, scale)
  }
}

$("input").trigger("input")
canvas { border: 1px solid red }
input { position: absolute }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input oninput="update(this)" type="range" min="1" max="20">
<canvas id="canvas"></canvas>

Answer №2

Reduce the number of calculations required in your sketches.

All shapes have identical widths and heights, along with a consistent offset from the preceding shape in the stack.
The camera position is the only variable element here.

To make changes, focus solely on adjusting the camera by modifying your context's transform matrix.

Follow these steps:

  • Move the context to the origin point of the first shape
  • Scale the context based on the current scale value
  • Skew the context by -1 on the Y axis
  • Draw your stack using fixed values

The initial three steps can be combined into one call to the absolute setTransform method.

The parameters will be as follows:

setTransform(
  scale,    // scale-x
  0,        // skew-x
  - scale,  // skew-y (using '- scale' since skew comes after scaling)
  scale,    // scale-y
  origin_x, // adjust prior to standard scaling
  origin_y
)

Once this is done, the drawing process remains consistent.

const canvas = document.getElementById("canvas")
canvas.width = canvas.height = 200;
const ctx = canvas.getContext("2d")
const input = document.querySelector('input');
function update() {
  // Constants for our shapes
  const origin_x = 160; // center of all rects
  const origin_y = 160; // bottom of shapes
  const w = 33; // rect width
  const h = 16.5; // rect height
  const offset = 11; // axis offset (* i)
  const scale = input.value;
  
  // Reset transformation matrix
  ctx.setTransform(1,0,0,1,0,0);
  // Clear everything
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  // Move context so origins are top-left
  // Already scaled and skewed
  ctx.setTransform(scale, 0, -scale, scale, origin_x, origin_y);
  
  // Scale no longer important from now on
  for (let i = 0; i < 4; i++) {
    ctx.fillStyle = (i === 2) ? "#3b2a19" : "#957e67"
    let layer = {
      x: -i * offset - w/2,
      y: -i * offset,
      width: w,
      height: h
    }
    ctx.fillRect(layer.x, layer.y, layer.width, layer.height)
    ctx.strokeRect(layer.x, layer.y, layer.width, layer.height)
  }
}
input.oninput = update;
input.oninput();
#canvas {
  border: 2px solid red
}
input {
  display: block;
  width: 75vw;
}
<input type="range" min="0.01" max="20" value="1" step="0.001" id="inp"/>
<canvas id="canvas"></canvas>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Identification of input change on any input or select field within the current modal using JavaScript

My modal contains approximately 20 input and select fields that need to be filled out by the user. I want to implement a JavaScript function to quickly check if each field is empty when the user navigates away or makes changes. However, I don't want t ...

The visibility of overflow-y does not seem to be working properly when overflow-x is

https://i.sstatic.net/hihjC.png .iati-list-table { overflow-x: auto; overflow-y: visible; } When I apply overflow-visible, a scroll bar appears. But when I use overflowy-hidden, the tooltip is cropped. How can I set it so that overflow x is auto a ...

What is the solution for correcting the fixed footer in jQuery Mobile?

My experience with jQueryMobile has led me to encounter a couple of persistent bugs even after including data-role="footer" data-position="fixed" in the markup: The footer toggles on a null click event. The footer remains unfixed and ends up hiding some ...

Dealing with validations in a personalized aid

Recently, I've been exploring CodeceptJs and have found it quite easy to work with. Currently, I'm utilizing it with NightmareJs for testing a gallery that retrieves data from an interface via JSONP to create a list of images enclosed in <div& ...

Retrieve the text located within the span tag using Selenium

When I try to inspect the element using Google Chrome, I come across the following code: <div class="form-style-abc"> <fieldset> <legend><span class="number">*</span>Search Zone</legend> I am trying to retrieve ...

.mouseleave() triggers when exiting a designated boundary area

I'm attempting to implement a roll-up feature for a div when the mouse is over another div. The issue I'm facing is that the roll-up div closes even if the mouse just exits through the bottom border. Is there a way to achieve this using JavaScrip ...

What is the best way to determine if my code is executing within a NodeJS environment?

I am working on a piece of JavaScript code that is specifically meant to run in NodeJS. It uses functions like require(), so running it elsewhere would cause errors. I want to add a runtime check to ensure the code is only executed within NodeJS and displa ...

Ways to conceal images until AFTER the completion of the jquery flexslider loading process

After trying to integrate wootheme's Flexslider on my website, I encountered a small issue with its loading process. Whenever the page is refreshed with the slider, there is a brief moment (approximately 1 second) where the first slide appears overly ...

Is there a way to import a module generated by typescript using its name directly in JavaScript?

I am trying to bring a function from a typescript-generated module into the global namespace of JavaScript. The typescript module I have is called foo.ts: export const fooFn = (): string => { return "hello"; }; Below is the structure of my HTML file ...

Endless cycle of React hooks

I am struggling to understand why I keep encountering an infinite loop in my useClick function. I can see that I am updating the state value inside the useEffect using setVal, but according to the second parameter, useEffect should only run on onClick as ...

Is there a potential race condition in React when switching between lists?

I'm currently working on setting up a queue system where users can move items between different lists, such as from "available" to "with client". The queue's state is managed in the root React component like this: this.state = { queue: { a ...

Adjust the alignment of two headers with varying sizes

Can someone provide guidance on aligning two headers of different sizes (e.g. h3 and h5) based on their bottom edges, making them appear like a cohesive group in a sentence format? The current code snippet I am working with is as follows: ...

Having trouble achieving the desired effect with inline block

I'm having some trouble creating a simple store page. One of the products is supposed to look like this: However, it's currently showing up like this: I've been attempting to use inline block so that I can have the negotiate button and pro ...

Move items between two tree views using Javascript and jQuery drag and drop functionality

Recently, I have been on a quest to find drag and drop functionality for moving tree listing items between two separate tree views. While I did come across some solutions that work within a single tree, I have yet to find one that specifically caters to tr ...

Understanding the reading of Java Class List in Vue Js

As a beginner in Front-end development, I am trying to figure out how I can use Vue.js to read a Java class List. Specifically, I have a Java class that includes a list of fruits and I want to be able to display this list on the UI using Vue.js: public c ...

Implement a slideshow feature that automatically changes a CSS attribute when preview images are shown

(Apologies for the perplexing title) My intention was to create a slideshow gallery, and I stumbled upon an example here: https://www.w3schools.com/howto/howto_js_slideshow_gallery.asp In this setup, you'll notice previews of images in a small bar b ...

Having an issue with deleting a table row using jQuery when the button is clicked

I am facing an issue where clicking on a button does not delete the table row despite both the tr id and button id having the same value. Can someone please guide me on what might be going wrong? <tr id="<?php echo $result->id;?>"> <t ...

Combine the promises from multiple Promise.all calls by chaining them together using the array returned from

I've embarked on creating my very own blogging platform using node. The code I currently have in place performs the following tasks: It scans through various folders to read `.md` files, where each folder corresponds to a top-level category. The dat ...

Angular JS is failing to update views when passing dynamic IDs

I have implemented a method in my controller where I pass a dynamic id using the ng-init function, fetch a response through ajax calls, and try to print the response. However, I am facing an issue as the response is not getting printed. I am using ng-repea ...

Filtering in AngularJS based on a specific ID

Currently, I am dealing with a JSON encoded response from PHP that looks like this... [ {"ID":"149","IDusr":"4","aut_more_info":"good","doc_name":"img1838142879.jpeg","doc_type":"jpg"},{"ID":"149","IDusr":"4","aut_more_info":"good","img5733250433.jpeg","d ...