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

Errors are not being shown on mandatory fields in the form

After watching a tutorial video, I attempted to create a sign-up form. However, despite following all the steps, I encountered an issue where the Surname and Given name fields, which I set as required fields, were still processing blank when the form was s ...

When attempting to add objects to an indexedDB object store, an InvalidStateError is encountered

I have defined IndexedDB and IDBTransaction dbVersion = 1; var db; var dbreq; var customerData = { ssn: "444", name: "Bill", age: 35, email: "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d2b0bbbebe92b1bdbfa2b3bcabfcb1bdbf"& ...

Is it possible to provide unrestricted support for an infinite number of parameters in the typing of the extend function from Lodash

I am utilizing the "extend" function from lodash to combine the objects in the arguments as follows: import { extend } from 'lodash'; const foo1 = { item: 1 }; const foo2 = { item: 1 }; const foo3 = { item: 1 }; const foo4 = { item: 1 }; const f ...

Array contains a copy of an object

The outcome I am striving for is: dataset: [ dataset: [ { seriesname: "", data: [ { value: "123", }, { value: &q ...

Switch up the Javascript popup code without any specific pattern

I am looking to add variety to my pop up ads on my website by randomly changing the Javascript code for each ad every time a page is loaded. How can I achieve this? Script 1 <script type="text/javascript"> var uid = '12946'; var w ...

What steps should I take to create a plugin for a library if defining it as a peerDependency does not provide a specific implementation for me to work with?

Requirements for My Plugin: I'm currently in the process of developing a new plugin that is dependent on popularLibrary.js. Here are the key points about my plugin: It will not function properly if popularLibrary.js is missing. It is designed to wo ...

Shine a light on the input with a captivating outer

Can we replicate the outer glow effect that Safari and other browsers automatically add to an input field without using jQuery? If you want to remove it, check out this article: Thank you! ...

Searching for values within an array using the ".includes" method

I'm curious if there's a method to determine if a string contains any characters that are also present in an array? const array = ["cake", "hello", "ok"]; const string = "hello"; let result = string.include ...

Retrieve the image source when clicking on multiple posts that share the same div class and button class

<script src="https://code.jquery.com/jquery-1.11.1.js"></script> <script> $(function(){ $('.like').click(function(e){ e.preventDefault(); // stop the page from reloading alert( $('.post-container&apos ...

The arrows on the Slick Slider appear cropped on ios devices like the iphone 8, however, they display perfectly when tested on Chrome and Firefox

I am currently facing an issue with my slick slider setup at https://www.labtag.com/shop/product/clear-cryogenic-labels-for-frozen-vials-1-75-x-1-aha-224/. It is configured for 4 slides on desktop and 2 slides on mobile devices (768px breakpoint). The pr ...

Exploring the world of CSS font families in Sublime Text 3 and linking them to the web

Is there a way to connect a font from the internet (such as those on Google Fonts: ) to my Sublime Text CSS file? I have tried linking it to an HTML file that is already linked to my CSS code, but it hasn't been successful. ...

Guide: How to transfer the output from MongoDB in Node.js to AngularJS?

Currently, I am using Node.js to query my data from a MongoDB database. However, I am facing an issue where the data is not being sent out as expected when running this particular function. var findIco = function(db, callback) { var cursor = db.collec ...

The dropdown menu vanishes from sight as soon as the cursor moves away from a link

Recently, I encountered an issue while trying to create a dropdown menu using Jquery. The problem arose when attempting to select the second link, as the entire menu would disappear. Additionally, is there a way to ensure that only one dropdown menu is vis ...

Disregard the popstate triggered by Safari's Initial Page Load

Utilizing pushState, I am able to generate the page view on popstate events based on the history.state of the current page. In cases where there is no history.state present, my intention is to reload the document (hopefully). window.onpopstate = function ...

Photos inside a flexbox do not resize correctly

When placing 2 images inside a flexbox with the column direction, I anticipated that the images would resize accordingly when adjusting browser window size. However, this is not the case. Regardless of the CSS styles applied, I am unable to maintain the o ...

Execute the assignment of exports.someFunction from within a callback function

My Express route is set up like this: app.get('/api/:type/:id', api.getItemById); The function api.getItemById resides in the api module within routes. However, inside the api module, I need to execute a function that connects to the database a ...

Tsyringe - Utilizing Dependency Injection with Multiple Constructors

Hey there, how's everyone doing today? I'm venturing into something new and different, stepping slightly away from the usual concept but aiming to accomplish my goal in a more refined manner. Currently, I am utilizing a repository pattern and l ...

Understanding JavaScript's JSON Path Variables

I've scoured the internet for solutions to a similar issue but haven't been able to find any helpful information yet. My current challenge involves accessing a picture path (in JSON format) based on the material type of the selected element. Her ...

Material-UI: Avoid onClick event firing when clicking on an element that is overlapped by another in a sticky Table

I have a unique setup in my table where each row, including the header row, begins with a checkbox. This header row has a sticky property. As I scroll through the table, rows start to move behind the header row. If I try to click the checkbox in the heade ...

qunit timer reset

I have developed a user interface for manually launching qunit tests. However, I have noticed that the qunit test timer starts when displaying the interface, rather than when starting the actual test. For example: var myFunction = function (){ test ...