I'm trying to implement the following code in React JS. But, it behaves differently when resizing window. Any ideas?
function updatePointer() {
var windowWidth = $(window).width();
var windowHeight = $(window).height();
// Get largest dimension increase
var xScale = windowWidth / image.width;
var yScale = windowHeight / image.height;
var scale;
var yOffset = 0;
var xOffset = 0;
if (xScale > yScale) {
// The image fits perfectly in x axis, stretched in y
scale = xScale;
yOffset = (windowHeight - (image.height * scale)) / 2;
} else {
// The image fits perfectly in y axis, stretched in x
scale = yScale;
xOffset = (windowWidth - (image.width * scale)) / 2;
}
pointer.css('top', (target.y) * scale + yOffset);
pointer.css('left', (target.x) * scale + xOffset);
}
Fiddle code: http://jsfiddle.net/Tyriar/ypb5P/1/
Here is my code:
In your normal code target is always constant but in your react code this.state.left and this.state.top changes constantly
try adding target in react as well.
this.state = {
target: { x: 184, y: 88 },
left: 500,
top: 550,
};
and use it in setState
this.setState({
left: (this.state.target.x * scale) + xOffset,
top: (this.state.target.y * scale) + yOffset,
});
it will then behave in similar way.
Related
I have a parent div that displays a graph that can be rotated.
I want to place dots at mouse clicks. This is my place function:
module.exports.place = function (event, id) {
let div = document.createElement("div");
div.className = "container-fluid";
div.id = id;
let avakio_wrapper = document.getElementById("avakio");
avakio_wrapper.appendChild(div);
let parentPosition = getPosition(event.currentTarget);
xPosition = event.clientX - parentPosition.x - div.clientWidth / 2;
yPosition = event.clientY - parentPosition.y - div.clientHeight / 2;
div.style.left = xPosition + "px";
div.style.top = yPosition + "px";
}
When the graph isn't rotated it works as expected, though when i rotate the graph it places the dot first under mouse click and then it applies the rotation transform and moves it. I have screenshots showing what i describe. The red dot is showing the click position. I appreciate any help.
UPDATE!!!!
Here is an implementation of my code:
// Calculate clicking pos based of top-left corner of div
xPosition = event.clientX - parentPosition.x - dot.clientWidth / 2;
yPosition = event.clientY - parentPosition.y - dot.clientHeight / 2;
let centerx = parentDiv.clientWidth / 2;
let centery = parentDiv.clientHeight / 2;
// Find Rotation angle
let rot = parentDiv.style.getPropertyValue("transform");
if (rot != "") {
rot = rot.split("(")[1].split("deg")[0];
} else {
rot = 0;
}
// Perform Invert Rotation
xPosition = centerx - xPosition;
yPosition = centery - yPosition;
let xRot =
xPosition * Math.cos(rot * (Math.PI / 180)) +
yPosition * Math.sin(rot * (Math.PI / 180));
let yRot =
-xPosition * Math.sin(rot * (Math.PI / 180)) +
yPosition * Math.cos(rot * (Math.PI / 180));
// Calculate again based top-left corner
dot.style.left = centerx - xRot + "px";
dot.style.top = centery - yRot + "px";
However i run on some issues. When the parent div is rotated the dots are placed some pixels off based on the rotation angle. I have some screenshots of different rotation angles. I' ve done the maths on paper for many days but still can't figure out what causing it.
Here an application of the math.
They still move a bit (I think that is because of calculus imprecision). I'm kinda stuck here, maybe some will be able to fix it.
function rotate() {
let parent = document.getElementById("parent")
let rot = document.getElementById("value").value
let transform = parent.style.transform
transform = "rotate("+ (Number(transform.substr(7,transform.length-11)) + Number(rot)) +"deg)"
parent.style.transform = transform
let dots = document.getElementsByClassName("dot")
for (i = 0; i < dots.length; i++)
dotpos(parent, dots[i],rot)
}
function dotpos(parent,dot,rot) {
rot = rot * (Math.PI/180) //convert to radian
let xCenter = parent.offsetWidth /2
let yCenter = parent.offsetHeight /2
let x = xCenter - Number(dot.style.left.substring(0, dot.style.left.length - 2))
let y = yCenter - Number(dot.style.top.substring(0, dot.style.top.length - 2))
let xRot = x*Math.cos(rot)+y*Math.sin(rot)
let yRot = -x*Math.sin(rot)+y*Math.cos(rot)
dot.style.left = xCenter -xRot + "px"
dot.style.top = yCenter -yRot + "px"
}
#parent {
height: 100px;
width : 100px;
background-color: grey;
}
.dot {
height: 5px;
width : 5px;
border-radius: 5px;
background-color: red;
}
<div id="parent" style = "transform:rotate(0deg)">
<div class ="dot" style="position:relative;left:15px;top:30px;"></div>
<div class ="dot" style="position:relative;left:60px;top:50px;"></div>
</div>
<form>
<input type="text" id="value"><br>
</form>
<button onclick="rotate()">rotate</button>
I've read multiple suggestions on the issue of canvas blurriness on retina displays (e.g. using the window.devicePixelRatio approach; here, here and also here) but I haven't been able to apply the suggested solutions to my specific problem. The following script first creates a canvas with some random image data (which appear blurry), and then exports the image to a SVG element and rescales it (still blurry of course). I am using a MBP late 2016 with touch bar and safari. Any suggestions on how to avoid blurriness and achieve crisp edges? Keep in mind that the initial imageData should have a fixed width and height.
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<body></body>
<script type="text/javascript">
var width = 100;
var height = 100;
var canvas = d3.select("body").append("canvas");
context = canvas.node().getContext("2d"),
canvas
.attr("width", width)
.attr("height", height)
.style("width", width + "px")
.style("height", height + "px")
//this is the part that should normally take care of blurriness
if (window.devicePixelRatio > 1) {
var devicePixelRatio = window.devicePixelRatio || 1;
var backingStoreRatio = context.webkitBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
var ratio = devicePixelRatio / backingStoreRatio;
canvas
.attr('width', width * ratio)
.attr('height', height * ratio)
.style('width', width + 'px')
.style('height', height + 'px');
context.scale(ratio, ratio);
}
var imageData = context.createImageData(width, height);
for (var i = 0, l = 0; i<height; ++i) {
for (j = 0; j<width; ++j, l += 4) {
imageData.data[l+0] = Math.round( Math.random() * 255);
imageData.data[l+1] = Math.round( Math.random() * 255);
imageData.data[l+2] = Math.round( Math.random() * 255);
imageData.data[l+3] = Math.round( Math.random() * 255);
}
}
context.putImageData(imageData, 0, 0);
var ImageD = canvas.node().toDataURL("img/png");
var svg = d3.select('body').append('svg').attr('width', width*5).attr('height', height*5);
svg.append("svg:image").datum(ImageD).attr("xlink:href", function(d) {return d})
.attr("height", height*5).attr("width", width*5)
</script>
I've finally found the solution. I use a combination of the following: window.devicePixelRatio for getting the retina pixel ratio, off-screen canvas taken from here, and then scaling up the context taken from here
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<body></body>
<script type="text/javascript">
const width = 20;
const height = 20;
const scale = 10; // the higher the number the crisper the custom image
var canvas = d3.select("body").append("canvas");
context = canvas.node().getContext("2d");
const ratio = window.devicePixelRatio || 1;
canvas.attr('width', width * ratio * scale)
.attr('height', height * ratio * scale)
.style('width', width * scale + 'px')
.style('height', height * scale + 'px');
var imageData = context.createImageData(width, height);
for (var i = 0, l = 0; i<height; ++i) {
for (j = 0; j<width; ++j, l += 4) {
imageData.data[l+0] = Math.round( Math.random() * 255);
imageData.data[l+1] = Math.round( Math.random() * 255);
imageData.data[l+2] = Math.round( Math.random() * 255);
imageData.data[l+3] = Math.round( Math.random() * 255);
}
}
const offCtx = canvas.node().cloneNode().getContext('2d'); // create an off screen canvas
offCtx.putImageData(imageData, 0,0);
context.scale(ratio * scale, ratio * scale);
context.mozImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
context.drawImage(offCtx.canvas, 0,0);
//export image
var ImageD = canvas.node().toDataURL("img/png");
//load image
d3.select('body').append('svg').attr("height", 500).attr("width", 500).append("svg:image").datum(ImageD).attr("xlink:href", function(d) {return d})
.attr("height", 500).attr("width", 500);
</script>
Image data is not context aware.
The code you had would work if you used the context to render, but you are writing pixels directly to a image buffer. This is not effected by the 2D context transform and hence your code does not scale up.
In you code the line
context.scale(ratio, ratio);
that scales up the canvas rendering does not apply to the imagedata.
Simple fix
A simple fix if you know the device is retina. It doubles the canvas resolution and then sets random pixels. To keep with your original code I set 2 by 2 pixels to the same random value. The blur will be gone but the random pattern remain the same.
const width = 100;
const height = 100;
const w = width; // because I hate cluttered code
const h = height;
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
canvas.width = w * 2;
canvas.height = h * 2;
canvas.style.width = w + "px";
canvas.style.height = h + "px";
const imageData = ctx.createImageData(w * 2, h * 2);
// get 32bit view of data
const b32 = new Uint32Array(imageData.data.buffer);
// this is the part that you need to change as the canvas resolution is double
for (let i = 0, l = 0; i< h; i ++) {
for (let j = 0; j < w; j ++) {
const idx = i * w* 2 + j * 2;
b32[idx + w + 1] = b32[idx + w] = b32[idx + 1] = b32[idx] = (Math.random() * 0xFFFFFFFF) | 0;
}
}
ctx.putImageData(imageData, 0, 0);
const ImageD = canvas.toDataURL("img/png");
const svg = d3.select('body').append('svg').attr('width', width*5).attr('height', height*5);
svg.append("svg:image").datum(ImageD).attr("xlink:href", function(d) {return d})
.attr("height", height*5).attr("width", width*5)
I am setting up a gallery that will contain a set amount of columns and each column will possess a varying amount of elements within them. The logic is that you hover around the window width and height and are able to view the containing div relative to the height and width. I have managed to get the dynamic width of the element and successfully maneuver the div to so every column is visible - as you can see in the fiddle here https://jsfiddle.net/x66zqwaa/ (the jquery is underneath). What I don't understand is that horizontally i'm able to view each element successfully - but vertically some of the divs are cut off and it isn't behaving in the same way as it does horizontally. If anyone could please assist i'd be very grateful!
$(document).ready(function() {
var winW = $(window).width(),
winH = $(window).height(),
winWHalf = winW / 2,
winHHalf = winH / 2,
$cont = $(".content"),
contW = $cont.width(),
contH = $cont.height(),
diffX = (winW - contW) /2,
diffY = (winH - contH) /2;
var finalX = 0,
finalY = 0;
var moveContent = function(e) {
var x = e.pageX,
y = e.pageY;
finalX = ((x) * (contW / winW) / 2);
finalY = ((y) * (contH / winH) / 2);
console.log(finalX, finalY);
$cont.css("transform", "translate3D(" + (0 - finalX) + "px, " + (0 - finalY) + "px, 0)");
}; $(document).ready(function() {
var winW = $(window).width(),
winH = $(window).height(),
winWHalf = winW / 2,
winHHalf = winH / 2,
$cont = $(".content"),
contW = $cont.width(),
contH = $cont.height(),
diffX = (winW - contW) /2,
diffY = (winH - contH) /2;
var finalX = 0,
finalY = 0;
var moveContent = function(e) {
var x = e.pageX,
y = e.pageY;
finalX = ((x) * (contW / winW) / 2);
finalY = ((y) * (contH / winH) / 2);
console.log(finalX, finalY);
$cont.css("transform", "translate3D(" + (0 - finalX) + "px, " + (0 - finalY) + "px, 0)");
};
Apologies but for some reason i'm not able to post the entirity of the code. Please see the fiddle.
You are calculating width and height based on $(".content"). You need to use either $(window) or $(document) instead. For height, you might want to use the bigger value, to make sure that you fill the screen.
I would like to fill a div with an image (but not stretch) just like in this post CSS Image size, how to fill, not stretch? but instead of using CSS I need to calculate values using JavaScript.
This is what I have:
image.onload = ()=> {
var ratio: number = image.width / image.height;
if (ratio > 1) {
image.height = this._height;
image.width = ratio * this._height;
image.style.left = -((image.width - this._width) / 2) + "px";
} else {
ratio = 1 / ratio;
image.width = this._width;
image.height = ratio * this._width;
image.style.top = -((image.height - this._height) / 2) + "px";
}
};
this is the div and image is a normal Image().
It works in most cases but not when for example this._width < ratio*this._height.
How can I get the algorithm to work for all cases? I know it's pretty simple but I can't get it to work.
I think the problem is that you compare ratio with 1, but you should compare it with div's ratio:
image.onload = ()=> {
var imgRatio: number = image.width / image.height,
divRatio: number = this._width / this._height;
if (imgRatio > divRatio) {
image.height = this._height;
image.width = this._height * imgRatio;
image.style.left = -((image.width - this._width) / 2) + "px";
} else {
image.width = this._width;
image.height = this._width / imgRatio;
image.style.top = -((image.height - this._height) / 2) + "px";
}
};
Im not sure if this will helps but this is the function that I have used in the past for setting image sizes for the canvas element.
The image will take up the whole element without skewing it.
function setCanvasImage(canvas, source) {
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var context = canvas.getContext('2d');
var image = new Image();
image.src = source;
image.onload = function () {
var sourceWidth = canvasWidth / canvasHeight * image.height;
var sourceX = (image.width - sourceWidth) / 2;
if(sourceX > 0) {
var sourceY = 0;
var sourceHeight = image.height;
} else {
var sourceX = 0;
var sourceWidth = image.width;
var sourceHeight = canvasHeight / canvasWidth * image.width;
var sourceY = (image.height - sourceHeight) / 2;
}
//placing
var destinationX = 0;
var destinationY = 0;
var destinationWidth = canvas.width;
var destinationHeight = canvas.height;
context.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destinationX, destinationY, destinationWidth, destinationHeight);
}
}
There are 2 scale values that you need to calculate, the scale value that makes the image as wide as the container, and the scale value that makes the image as tall as the container. To scale the image to fit inside this container, you choose the smaller of these scales, and to scale the image to extend beyond the container, you choose the largest. You don't care about the ratio between the width and height of the image. Example of fitting the image inside the container and centring:
var xScale = _width/img.width; // Scale by this much to fit x
var yScale = _height/img.height; // Scale by this much to fit y
var width = _width;
var height = _height;
var top = 0;
var left = 0;
if (xScale !== yScale) {
// Choose the smaller scale. To make the image extend, make this >
if (xScale < yScale) {
height = Math.round(xScale * height);
top = Math.round((_height - height) / 2);
} else {
width = Math.round(yScale * width);
left = Math.round((_width - width) / 2);
}
}
img.width = width;
img.height = height;
img.top = top + "px";
img.left = left + "px";
I have an image drawn to an html cavas. I would like to be able to rotate the image by 90 degrees but I can't seem to find an example on how to rotate the entire canvas image, only an object on the canvas.
Does anyone have example code to rotate an entire canvas by 90 degrees?
I accepted an anwser below but wanted to provide additional example code : http://jsfiddle.net/VTyNF/1/
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.translate(canvas.width/2,canvas.height/2)
context.rotate(90 * (Math.PI / 180));
context.beginPath();
context.rect(188 - canvas.width/2, 50 - canvas.height/2, 200, 100);
context.fillStyle = 'yellow';
context.fill();
context.lineWidth = 7;
context.strokeStyle = 'black';
context.stroke();
</script>
You would have to apply this rotation before you draw your canvas. You can't rotate anything that is already drawn.
So:
To rotate a canvas, the content.rotate() expects a value in radians. So first, lets make it simple by converting degrees to radians using:
function getRadianAngle(degreeValue) {
return degreeValue * Math.PI / 180;
}
You may want to translate the canvas first before rotating so that it's origin is set correctly.
context.translate(context.width/2,context.height/2);
Once we know what value we want, we simply rotate the canvas before we draw anything!
Please note, in your example, the rectangle you have drawn, is also being offset in the first two parameters of context.rect(X,Y,W,H)`.
I find it's easier to set widths as variables, then do simple math to re position the box automatically, notice now it sits perfectly in the center, and rotates nicely!
DEMO: http://jsfiddle.net/shannonhochkins/VTyNF/6/
Say your canvas element has id "foo". In JavaScript you could do something like this:
var canvas = document.getElementById('foo'),
context = canvas.getContext('2d');
// Rotates the canvas 90 degrees
context.rotate(90 * (Math.PI / 180));
Could you use CSS to rotate the <canvas> element with transform: rotate(90deg);?
You can easily rotate the image ninety degrees by manipulating the pixel data. If your canvas isn't square, you will have to make some choices about what the 'correct' answer will be.
Use the getImageData function to retrieve the pixels, manipulate them in the usual manner, and use putImageData to display the result.
This version doesn't require center point for 90 degree turn:
(Not as easy becase it's a 1d array with 4 values per pixel, initialDimensions means horizontal or 0, 180 rotation state vs 90, 270)
//...
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d', {willReadFrequently: true});
const img = document.createElement('img');
const file = inp.files[0]; // file from user input
img.src = URL.createObjectURL(file);
img.initialDimensions = true;
img.addEventListener('load', function () {
canvas.width = this.width;
canvas.height = this.height;
canvas.crossOrigin = "anonymous";
context.drawImage(img, 0, 0);
rotateClockwiseBy90(canvas, context, img);
}
}
function rotateClockwiseBy90(canvas, context, img) {
if (img.initialDimensions == undefined) {
img.initialDimensions = true;
}
var width, height;
if (img.initialDimensions) {
width = img.naturalWidth;
height = img.naturalHeight;
img.initialDimensions = false;
}
else {
width = img.naturalHeight;
height = img.naturalWidth;
img.initialDimensions = true;
}
const imageData = context.getImageData(0, 0, width, height);
const rotatedImageData = context.createImageData(height, width);
//[redIndex, greenIndex, blueIndex, alphaIndex]
const width4 = width * 4;
const height4 = height * 4;
for (let y = 0; y < height4; y += 4) {
for (let x = 0; x < width4; x += 4) {
rotatedImageData.data[x * height + (height4 - y -1) - 3] = imageData.data[y * width + x];
rotatedImageData.data[x * height + (height4 - y -1) - 2] = imageData.data[y * width + x + 1];
rotatedImageData.data[x * height + (height4 - y -1) - 1] = imageData.data[y * width + x + 2];
rotatedImageData.data[x * height + (height4 - y -1)] = imageData.data[y * width + x + 3];
}
}
const cw = canvas.width;
canvas.width = canvas.height;
canvas.height = cw;
context.putImageData(rotatedImageData, 0, 0);
}
If someone is trying to understand the logic:
rotatedImageData.data[x * height ...
should really be:
rotatedImageData.data[x / 4 * height * 4 ...
because for the rotated array x represents row number and height represents row length, but the result is same.
Version for counterclockwise rotation:
for (let y = 0; y < height4; y += 4) {
for (let x = 0; x < width4; x += 4) {
rotatedImageData.data[(height4 * (width - x/4) - height4 + y)] = imageData.data[y * width + x];
rotatedImageData.data[(height4 * (width - x/4) - height4 + y) + 1] = imageData.data[y * width + x + 1];
rotatedImageData.data[(height4 * (width - x/4) - height4 + y) + 2] = imageData.data[y * width + x + 2];
rotatedImageData.data[(height4 * (width - x/4) - height4 + y) + 3] = imageData.data[y * width + x + 3];
}
}