Canvas arc on Retina is too sharp at some points - javascript

I am trying to create animated arc that doesn't look blurry on HiDPI devices.
This is how my arc looks on iPhone 5s:
you can see that near 0, 90, 180 deg arc becomes too sharp. How can I prevent this?
This is my code:
// Canvas arc progress
const can = document.getElementById('canvas');
const ctx = can.getContext('2d');
const circ = Math.PI * 2;
const quart = Math.PI / 2;
const canvasSize = can.offsetWidth;
const halfCanvasSize = canvasSize / 2;
let start = 0,
finish = 70,
animRequestId = null;
// Get pixel ratio
const ratio = (function() {
const dpr = window.devicePixelRatio || 1,
bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
return dpr / bsr;
})();
// Set canvas h & w
can.width = can.height = canvasSize * ratio;
can.style.width = can.style.height = canvasSize + 'px';
ctx.scale(ratio, ratio)
ctx.beginPath();
ctx.strokeStyle = 'rgb(120,159,194)';
ctx.lineCap = 'square';
ctx.lineWidth = 8.0;
ctx.arc(halfCanvasSize, halfCanvasSize, halfCanvasSize - 4, 0, circ, false);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = 'rgb(244,247,255)';
ctx.lineCap = 'round';
ctx.lineWidth = 8.0;
ctx.closePath();
let imd = ctx.getImageData(0, 0, canvasSize, canvasSize);
const draw = (current) => {
ctx.putImageData(imd, 0, 0);
ctx.beginPath();
ctx.arc(halfCanvasSize, halfCanvasSize, halfCanvasSize - 4, -(quart), ((circ) * current) - quart, false);
ctx.stroke();
};
(function animateArcProgress() {
animRequestId = requestAnimationFrame(animateArcProgress);
if (start <= finish) {
draw(start / 100);
start += 2;
} else {
cancelAnimationFrame(animRequestId);
}
})();
body {
margin: 0;
}
div {
display: flex;
align-items: center;
justify-content: center;
height: 300px;
widht: 300px;
background: #85b1d7;
}
canvas {
height: 250px;
width: 250px;
}
<div>
<canvas id='canvas'></canvas>
</div>

You can soften the edge by drawing 1/2 pixel inside as done in your code below.
I rendered the arc 3 times at 8, 7.5 and 7 pixel width width alpha colour values 0.25, 0.5 and 1 respectively.
You can make it as soft as you want.
BTW using putImageData is very slow, why not just render the background to another canvas and draw that via ctx.drawImage(offScreencanvas,0,0) that way you will use the GPU to render the background rather than the CPU via the graphics port bus.
I added a bit more code to show the different softening FX you can get and added a mouse zoom so you can see the pixels a little better.
const can = document.getElementById('canvas');
const can2 = document.createElement("canvas"); // off screen canvas
const can3 = document.createElement("canvas"); // off screen canvas
const ctx = can.getContext('2d');
const ctx2 = can2.getContext('2d');
const ctx3 = can3.getContext('2d');
const circ = Math.PI * 2;
const quart = Math.PI / 2;
const canvasSize = can.offsetWidth;
const halfCanvasSize = canvasSize / 2;
const mouse = {x : null, y : null};
can.addEventListener("mousemove",function(e){
var bounds = can.getBoundingClientRect();
mouse.x = e.clientX - bounds.left;
mouse.y = e.clientY - bounds.top;
});
let start = 0,
finish = 70,
animRequestId = null;
// Get pixel ratio
const ratio = (function() {
const dpr = window.devicePixelRatio || 1,
bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
return dpr / bsr;
})();
// Set canvas h & w
can2.height = can3.height = can2.width = can3.width = can.width = can.height = canvasSize * ratio;
can.style.width = can.style.height = canvasSize + 'px';
ctx.scale(ratio, ratio)
ctx2.scale(ratio, ratio)
ctx3.scale(ratio, ratio)
ctx2.beginPath();
ctx2.strokeStyle = 'rgb(120,159,194)';
ctx2.lineCap = 'square';
ctx2.lineWidth = 8.0;
ctx2.arc(halfCanvasSize, halfCanvasSize, halfCanvasSize - 4, 0, circ, false);
ctx2.stroke();
ctx2.closePath();
ctx2.beginPath();
ctx2.strokeStyle = 'rgb(244,247,255)';
ctx2.lineCap = 'round';
ctx2.lineWidth = 8.0;
ctx2.closePath();
const draw = (current) => {
ctx3.clearRect(0,0,canvas.width,canvas.height);
ctx3.drawImage(can2,0,0);
var rad = halfCanvasSize - 4;
const drawArc = () => {
ctx3.beginPath();
ctx3.arc(halfCanvasSize, halfCanvasSize, rad, -(quart), ((circ) * current) - quart, false);
ctx3.stroke();
}
// draw soft
ctx3.strokeStyle = 'rgb(244,247,255)';
ctx3.lineWidth = 8.5;
ctx3.globalAlpha = 0.25;
drawArc();;
ctx3.lineWidth = 7.0;
ctx3.globalAlpha = 0.5;
drawArc();;
ctx3.lineWidth = 6.5;
ctx3.globalAlpha = 1;
drawArc();
// draw normal
rad -= 12;
ctx3.lineWidth = 8.0;
ctx3.globalAlpha = 1;
drawArc();;
// draw ultra soft
rad -= 12;
ctx3.strokeStyle = 'rgb(244,247,255)';
ctx3.lineWidth = 9.0;
ctx3.globalAlpha = 0.1;
drawArc();
ctx3.lineWidth = 8.0;
ctx3.globalAlpha = 0.2;
drawArc();;
ctx3.lineWidth = 7.5;
ctx3.globalAlpha = 0.5;
drawArc();
ctx3.lineWidth = 6;
ctx3.globalAlpha = 1;
drawArc();
};
const zoomW = 30;
const zoomAmount = 5;
const drawZoom = () => {
ctx.drawImage(can3,0,0);
var width = zoomW * zoomAmount;
var cx = mouse.x - width / 2;
var cy = mouse.y - width / 2;
var c1x = mouse.x - zoomW / 2;
var c1y = mouse.y - zoomW / 2;
ctx.strokeStyle = 'rgb(244,247,255)';
ctx.lineWidth = 4;
ctx.strokeRect(cx,cy,width,width);
ctx.clearRect(cx,cy,width,width);
ctx.imageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.drawImage(can3,c1x,c1y,zoomW,zoomW,cx,cy,width,width);
ctx.imageSmoothingEnabled = true;
ctx.mozImageSmoothingEnabled = true;
}
function keepUpdating(){
ctx.clearRect(0,0,can.width,can.height);
drawZoom();
requestAnimationFrame(keepUpdating);
}
(function animateArcProgress() {
ctx.clearRect(0,0,can.width,can.height);
draw(start / 100);
drawZoom();
if (start <= finish) {
start += 0.5;
requestAnimationFrame(animateArcProgress);
} else {
requestAnimationFrame(keepUpdating);
}
})();
body {
margin: 0;
}
div {
display: flex;
align-items: center;
justify-content: center;
height: 300px;
widht: 300px;
background: #85b1d7;
}
canvas {
height: 250px;
width: 250px;
}
<div>
<canvas id='canvas'></canvas>
</div>

Related

Determine if a Selection Marquee is over a Rotated Rectangle

I have a Rectangle class for drawing to HTML Canvas. It has a rotation property that gets applied in its draw method. If the user drags within the canvas, a selection marquee is being drawn. How can I set the Rectangle's active attribute to true when the Rectangle is within the selection marquee using math? This is a problem I'm having in another language & context so I do not have all of Canvas' methods available to me there (e.g. isPointInPath).
I found a StackOverflow post about finding Mouse position within rotated rectangle in HTML5 Canvas, which I am implementing in the Rectangle method checkHit. It doesn't account for the selection marquee, however. It's just looking at the mouse X & Y, which is still off. The light blue dot is the origin around which the rectangle is being rotated. Please let me know if anything is unclear. Thank you.
class Rectangle
{
constructor(x, y, width, height, rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.xOffset = this.x + this.width/2;
this.yOffset = this.y + ((this.y+this.height)/2);
this.rotation = rotation;
this.active = false;
}
checkHit()
{
// translate mouse point values to origin
let originX = this.xOffset;
let originY = this.yOffset;
let dx = marquee[2] - originX;
let dy = marquee[3] - originY;
// distance between the point and the center of the rectangle
let h1 = Math.sqrt(dx*dx + dy*dy);
let currA = Math.atan2(dy,dx);
// Angle of point rotated around origin of rectangle in opposition
let newA = currA - this.rotation;
// New position of mouse point when rotated
let x2 = Math.cos(newA) * h1;
let y2 = Math.sin(newA) * h1;
// Check relative to center of rectangle
if (x2 > -0.5 * this.width && x2 < 0.5 * this.width && y2 > -0.5 * this.height && y2 < 0.5 * this.height){
this.active = true;
} else {
this.active = false;
}
}
draw()
{
ctx.save();
ctx.translate(this.xOffset, this.yOffset);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 2 * Math.PI, true);
ctx.fill();
ctx.rotate(this.rotation * Math.PI / 180);
ctx.translate(-this.xOffset, -this.yOffset);
if (this.active)
{
ctx.fillStyle = 'rgba(255,0,0,0.5)';
} else {
ctx.fillStyle = 'rgba(0,0,255,0.5)';
}
ctx.beginPath();
ctx.fillRect(this.x, this.y, this.width, this.y+this.height);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;
var rect = new Rectangle(50,50,90,30,45);
var marquee = [-3,-3,-3,-3];
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
var start_x,start_y;
let draw = () => {
ctx.clearRect(0,0, canvas.width, canvas.height);
//rect.rotation+=1;
rect.draw();
ctx.fillStyle = "rgba(200, 200, 255, 0.5)";
ctx.fillRect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.strokeStyle = "white"
ctx.lineWidth = 1;
ctx.rect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.stroke()
raf = window.requestAnimationFrame(draw);
}
let dragStart = (e) =>
{
start_x = parseInt(e.clientX-offsetX);
start_y = parseInt(e.clientY-offsetY);
marquee = [start_x,start_y,0,0];
canvas.addEventListener("mousemove", drag);
}
let drag = (e) =>
{
let mouseX = parseInt(e.clientX-offsetX);
let mouseY = parseInt(e.clientY-offsetY);
marquee[2] = mouseX - start_x;
marquee[3] = mouseY - start_y;
rect.checkHit();
}
let dragEnd = (e) =>
{
marquee = [-10,-10,-10,-10];
canvas.removeEventListener("mousemove", drag);
}
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('mouseup', dragEnd);
raf = window.requestAnimationFrame(draw);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>
Do convex polygons overlap
Rectangles are convex polygons.
Rectangle and marquee each have 4 points (corners) that define 4 edges (lines segments) connecting the points.
This solution works for all convex irregular polygons with 3 or more sides.
Points and edges must be sequential either Clockwise CW of Count Clockwise CCW
Test points
If any point of one poly is inside the other polygon then they must overlap. See example function isInside
To check if point is inside a polygon, get cross product of, edge start to the point as vector, and the edge as a vector.
If all cross products are >= 0 (to the left of) then there is overlap (for CW polygon). If polygon is CCW then if all cross products are <= 0 (to the right of) there is overlap.
It is possible to overlap without any points inside the other poly.
Test Edges
If any of the edges from one poly crosses any of the edges from the other then there must be overlap. The function doLinesIntercept returns true if two line segments intercept.
Complete test
Function isPolyOver(poly1, poly2) will return true if there is overlap of the two polys.
A polygon is defined by a set of Point's and Lines's connecting the points.
The polygon can be irregular, meaning that each edge can be any length > 0
Do not pass polygons with an edge of length === 0 or will not work.
Added
I added the function Rectangle.toPoints that transforms the rectangle and returning a set of 4 points (corners).
Example
Example is a copy of your code working using the above methods.
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('mouseup', dragEnd);
requestAnimationFrame(draw);
const Point = (x = 0, y = 0) => ({x, y, set(x,y){ this.x = x; this.y = y }});
const Line = (p1, p2) => ({p1, p2});
const selector = { points: [Point(), Point(), Point(), Point()] }
selector.lines = [
Line(selector.points[0], selector.points[1]),
Line(selector.points[1], selector.points[2]),
Line(selector.points[2], selector.points[3]),
Line(selector.points[3], selector.points[0])
];
const rectangle = { points: [Point(), Point(), Point(), Point()] }
rectangle.lines = [
Line(rectangle.points[0], rectangle.points[1]),
Line(rectangle.points[1], rectangle.points[2]),
Line(rectangle.points[2], rectangle.points[3]),
Line(rectangle.points[3], rectangle.points[0])
];
function isInside(point, points) {
var i = 0, p1 = points[points.length - 1];
while (i < points.length) {
const p2 = points[i++];
if ((p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x) < 0) { return false }
p1 = p2;
}
return true;
}
function doLinesIntercept(l1, l2) {
const v1x = l1.p2.x - l1.p1.x;
const v1y = l1.p2.y - l1.p1.y;
const v2x = l2.p2.x - l2.p1.x;
const v2y = l2.p2.y - l2.p1.y;
const c = v1x * v2y - v1y * v2x;
if(c !== 0){
const u = (v2x * (l1.p1.y - l2.p1.y) - v2y * (l1.p1.x - l2.p1.x)) / c;
if(u >= 0 && u <= 1){
const u = (v1x * (l1.p1.y - l2.p1.y) - v1y * (l1.p1.x - l2.p1.x)) / c;
return u >= 0 && u <= 1;
}
}
return false;
}
function isPolyOver(p1, p2) { // is poly p2 under any part of poly p1
if (p2.points.some(p => isInside(p, p1.points))) { return true };
if (p1.points.some(p => isInside(p, p2.points))) { return true };
return p1.lines.some(l1 => p2.lines.some(l2 => doLinesIntercept(l1, l2)));
}
const ctx = canvas.getContext("2d");
var dragging = false;
const marquee = [0,0,0,0];
const rotate = 0.01;
var startX, startY, hasSize = false;
const BB = canvas.getBoundingClientRect();
const offsetX = BB.left;
const offsetY = BB.top;
class Rectangle {
constructor(x, y, width, height, rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.rotation = rotation;
this.active = false;
}
toPoints(points = [Point(), Point(), Point(), Point()]) {
const xAx = Math.cos(this.rotation) / 2;
const xAy = Math.sin(this.rotation) / 2;
const x = this.x, y = this.y;
const w = this.width, h = this.height;
points[0].set(-w * xAx + h * xAy + x, -w * xAy - h * xAx + y);
points[1].set( w * xAx + h * xAy + x, w * xAy - h * xAx + y);
points[2].set( w * xAx - h * xAy + x, w * xAy + h * xAx + y);
points[3].set(-w * xAx - h * xAy + x, -w * xAy + h * xAx + y);
}
draw() {
ctx.setTransform(1, 0, 0, 1, this.x, this.y);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.strokeStyle = this.active ? 'rgba(255,0,0,1)' : 'rgba(0,0,255,1)';
ctx.lineWidth = this.active ? 3 : 1;
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 2 * Math.PI, true);
ctx.fill();
ctx.rotate(this.rotation);
ctx.beginPath();
ctx.rect(-this.width / 2, - this.height / 2, this.width, this.height);
ctx.stroke();
}
}
function draw(){
rect.rotation += rotate;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect.draw();
drawSelector();
requestAnimationFrame(draw);
}
function drawSelector() {
if (dragging && hasSize) {
rect.toPoints(rectangle.points);
rect.active = isPolyOver(selector, rectangle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = "rgba(200, 200, 255, 0.5)";
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.rect(...marquee);
ctx.fill();
ctx.stroke();
} else {
rect.active = false;
}
}
function dragStart(e) {
startX = e.clientX - offsetX;
startY = e.clientY - offsetY;
drag(e);
canvas.addEventListener("mousemove", drag);
}
function drag(e) {
dragging = true;
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
const left = Math.min(startX, x);
const top = Math.min(startY, y);
const w = Math.max(startX, x) - left;
const h = Math.max(startY, y) - top;
marquee[0] = left;
marquee[1] = top;
marquee[2] = w;
marquee[3] = h;
if (w > 0 || h > 0) {
hasSize = true;
selector.points[0].set(left, top);
selector.points[1].set(left + w, top);
selector.points[2].set(left + w, top + h);
selector.points[3].set(left , top + h);
} else {
hasSize = false;
}
}
function dragEnd(e) {
dragging = false;
rect.active = false;
canvas.removeEventListener("mousemove", drag);
}
const rect = new Rectangle(canvas.width / 2, canvas.height / 2, 90, 90, Math.PI / 4);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>

How to visualize a sine wave with custom frequency and amplitude?

I am trying to plot a sine wave into the canvas: I want to control the frequency like if I say:
10 freq, I want to see 10 periods of oscillating waves with the same amplitude etc.
Currently 10 freq works ok, but If I say 49 frequency, I see a weird change in amplitude. What is causing this and what is my error?
In the example I overlap two different waves to save on space.
let element = document.getElementById('app');
let canvas = document.createElement('canvas');
canvas.width = 320;
canvas.height = 180;
let bounds = element.getBoundingClientRect();
canvas.style.width = bounds.width + 'px';
canvas.style.height = bounds.height + 'px';
element.appendChild(canvas);
let ctx = canvas.getContext('2d');
ctx.strokeStyle = 'red';
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 320, 180);
let points = [];
function fSin(freq, amplitude) {
return x =>
Math.sin(x / 100 * freq * Math.PI * 2) * amplitude;
}
function generate(ff) {
let res = [];
for (let i = 0; i < 100; i++) {
res.push([i, ff(i)]);
}
return res;
}
function drawPoints(points, xscale = 1, yscale = 1) {
ctx.moveTo(points[0][0] * xscale, 90 - points[0][1] * yscale);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0] * xscale, 90 - points[i][1] * yscale);
}
ctx.stroke();
}
points = generate(fSin(10, 10));
points = points.concat(generate(fSin(49, 10)));
drawPoints(points, 3.2, 2);
#app {
width: 90vw;
height: 90vh;
margin: auto;
}
#app canvas {
margin: auto;
}
<div id="app"></div>
What you're seeing is just an illusion due to aliasing, the interplay between the sampling rate (where the points are captured) and the sine function. If you were to zoom in and add more in-between points, you'd see that it's still a regular sine function. The omission of the in-between points due to sampling and connecting those samples with lines distorts the actual curve.
For fun, I modified your code a bit and added a slider. In this version, single pixel points are drawn instead, and the slider lets to interactively adjust the frequency. You can see as you increase/decrease the frequency how the samples seem to form weird patterns.
let element = document.getElementById('app');
let canvas = document.createElement('canvas');
canvas.width = 320;
canvas.height = 180;
canvas.style.width = '320px';
canvas.style.height = '180px';
let ctx = canvas.getContext('2d');
element.appendChild(canvas);
function fSin(freq, amplitude) {
return x => Math.sin(x / 100 * freq * Math.PI * 2) * amplitude;
}
let slider = document.createElement('input');
slider.type = 'range';
slider.min = 0;
slider.max = 100;
slider.addEventListener('change', () => {
const frequency = slider.value;
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 320, 180);
ctx.strokeStyle = 'red';
ctx.fillStyle = 'red';
const ff = fSin(frequency, 10);
for (let i = 0; i < canvas.width; i++) {
ctx.fillRect(i, 90 - ff(i) * 2, 1, 1);
}
});
element.appendChild(slider);
#app {
width: 90vw;
height: 90vh;
margin: auto;
}
#app canvas {
margin: auto;
}
<div id="app"></div>

canvas is stretching when changing device type in dev tool

I have next canvas with some content:
class ExpectedLifespanRound {
constructor() {
// max lifespan that is possible
this.lifespanMax = 125
// expected lifespan block
this.expLsBlock = document.getElementById("expected-lifespan-block")
// set style for the block
this.style = this.expLsBlock.style
this.style.padding = "20px"
this.style.display = "flex"
this.style.width = "320px"
this.style.height = "406px"
this.style.borderRadius = "20px"
this.style.backgroundColor = "white"
// set canvas style
this.can = this.initCanvas("expected-lifespan-block")
this.can.style.backgroundColor = "white";
// - 40 is padding
this.can.style.width = this.expLsBlock.outerWidth - 40 + "px"
this.can.style.height =this.expLsBlock.outerHeight - 40 + "px"
this.can.style.marginLeft = "8px"
this.expLsBlock.appendChild(this.can);
this.ctx = this.can.getContext("2d")
// set gradient for arc
this.gradient = this.ctx.createLinearGradient(320, 0, 0, 406);
this.gradient.addColorStop("0.3", "#ABB8FF")
this.gradient.addColorStop("0.7" ,"#ABF0FF")
}
clearCanvas() {
// clear canvas
this.ctx.clearRect(0, 0, 320, 406)
this.ctx.beginPath();
this.ctx.globalCompositeOperation = "source-over";
this.ctx.globalAlpha = 1;
this.ctx.closePath();
}
create(years) {
// title
this.ctx.save()
this.clearCanvas()
this.ctx.fillStyle = "black"
this.ctx.font = "bolder 18px Arial";
this.ctx.fillText("Ожидаемая", 100, 30)
this.ctx.fillText("продолжительность жизни", 32, 56)
this.ctx.restore()
// arc
this.ctx.save()
this.ctx.fillStyle = this.gradient
this.ctx.strokeStyle = this.gradient
this.ctx.lineWidth = 35
this.ctx.arc(150, 250, 125, Math.PI - (1.5 * Math.PI/this.lifespanMax)*years, 1.5 * Math.PI)
this.ctx.font = "bold 55px Arial"
var x = 120
if (years >= 100) {x = 105}
this.ctx.fillText(years, x, 245)
this.ctx.font = "bold 36px Arial"
this.ctx.fillText("лет", 120, 285)
this.ctx.stroke()
this.ctx.restore()
}
refresh(years) {
this.create(years)
}
initCanvas(blockForCanvas) {
// create high dpi canvas and put it in the block
// return pixel ratio
var getRatio = function () {
var ctx = document.createElement("canvas").getContext("2d");
var dpr = window.devicePixelRatio || 1;
var bsr =
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1;
return dpr / bsr;
};
// return high dots per inch canvas
var createHiDPICanvas = function (w, h) {
var ratio = getRatio();
var chart_container = document.getElementById(blockForCanvas);
var can = document.createElement("canvas");
can.style.backgroundColor = "white";
can.width = 320 * ratio;
can.height = 406 * ratio;
can.style.width = w + "px";
can.style.height = h + "px";
can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
chart_container.appendChild(can);
return can;
}
var canvas = createHiDPICanvas(this.scaleWidth, this.scaleHeight)
return canvas
}
}
var expLsRound = new ExpectedLifespanRound()
expLsRound.create(95)
body {
background-color:grey;
}
<div id="expected-lifespan-block"></div>
It is ok on my 1920x1080 screen, but when I switch type to mobile (line iphone 6, ipad or any other) - it is strating to stretch in not appropriate way. You can test it in chrome dev tools, or take a look on the pictures I tave taken:
normal:
stretched after switching to another device using dev tools:
How to avoid such a stretching and let this canvas be the same size no depending what device screen user has?
Just needed to replace 2 functions out if innerCanvas function and it does not stretch anymore

Moving an Object according to mouse coordinates

How do I get the following script to work? I wanted to be able to move the ship to the mouse coordinates. The space itself should move and the ship should stay in the centerview.
Would be great if you can modify the script as everybody writes his functions different.
Finally if the button is clicked I want the ship to move to that destination
I tried it when it worked and the ship never moved and I don't know if it has to do with the prototype.update function or not.
Is prototype.update or .render the same as if I would write this.update or this.render?
<script>
function domanDown(evt)
{
switch (evt)
{
case 38: /* Up arrow was pressed or button pressed*/
offsetY+=100 ;
break;
case 40: /* Down arrow was pressed or button pressed*/
offsetY-=100;
break;
case 37: /* Left arrow was pressedor button pressed*/
offsetX+=100;
break;
case 39: /* Right arrow was pressed or button pressed*/
offsetX-=100;
break;
}
}
window.requestAnimationFrame = function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(f) {
window.setTimeout(f,1e3/60);
}
}();
var Obj = function (x,y,sp,size){
this.size = size;
this.x = x;
this.y = y;
this.color = sp;
this.selected = 0;
}
var Planet_Class = function (x,y,sp){
this.type = 'Planet_Class';
this.depot = 11;
this.xtype = new Obj(x,y,sp,10);
w.objects.push(this);
Planet_Class.prototype.update =function () {
}
Planet_Class.prototype.render =function () {
ctx.save();
ctx.beginPath();
ctx.fillStyle = this.xtype.color;
ctx.fillRect(this.xtype.x, this.xtype.y,this.xtype.size,this.xtype.size);
ctx.fill();
ctx.restore();
}
}
var Usership = function(x,y,sp){
this.depot = 10;
this.type = 'Usership';
this.xtype = new Obj(x,y,sp,10);
w.objects.push(this);
Usership.prototype.update =function (x,y) {
this.xtype.x = x || 20;
this.xtype.y = y || 20;
}
Usership.prototype.render =function () {
ctx.save();
ctx.beginPath();
ctx.fillStyle = this.xtype.color;
ctx.fillRect(this.xtype.x, this.xtype.y,this.xtype.size,this.xtype.size);
ctx.fill();
ctx.restore();
}
}
var World = function(){
this.objects = new Array();
this.count = function(type,sp){
var cnt = 0;
for(var k = 0;k<this.objects.length;k++)
cnt++;
return cnt;
}
}
renderWorld = function(){
requestAnimationFrame(renderWorld);
var spliceArray = Array();
ctx.beginPath();
objcnt = w.objects.length;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "white";
ctx.fill();
i = 0;
while(i < objcnt){
w.objects[i].update();
w.objects[i].render();
if(w.objects[i].depot < 1)
spliceArray.push(i);
i++;
}
for(var k = 0;k<spliceArray.length;k++)
{
w.objects.splice(spliceArray[k],1); }
}
function r1(max,min){
return (Math.floor(Math.random() * (max - min)) + 1);
}
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d"),
width = 1024,
height = 768;
offsetX = (canvas.width/2)-300; /Startpoint x for the Ship
offsetY = (canvas.height/2)-300; /Startpoint y for the Ship
generateshipx = -offsetX + (canvas.width/2);
generateshipy = -offsetY + (canvas.height/2);
mX = generateshipx;
mY = generateshipy;
w = new World();
new Usership(generateshipx,generateshipy,'green');
for(i=1;i<4;i++){
new Planet_Class(r1(600,2),r1(600,2),'red');
}
canvas.addEventListener("click", function (e) {
mX = e.pageX- canvas.offsetLeft -offsetX) ;
mY = e.pageY - canvas.offsetTop -offsetY) ;
});
renderWorld();
</script>
<html>
<head>
</head>
<body style="background-color:black;">
<canvas style="z-index:1" width="1024" height="768" id="canvas"></canvas>
<input type="button" style="z-index:2; position:absolute; top:300; left:10" value="uo" onCLick="domanDown(38)()">
<input type="button" style="z-index:2; position:absolute; top:340; left:10" value="down" onCLick="domanDown(40)">
<input type="button" style="z-index:2; position:absolute; top:380; left:10" value="left" onCLick="domanDown(37)">
<input type="button" style="z-index:2; position:absolute; top:420; left:10" value="right" onCLick="domanDown(39)">
<input type="button" style="z-index:2; position:absolute; top:460; left:10" value="move" onCLick="moveObj()">
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
position: absolute;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
cursor: crosshair;
border: solid 1px white;
border-radius: 10px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
var canvasWidth = 180;
var canvasHeight = 160;
var canvas = null;
var ctx = null;
var bounds = null;
var isRotating = false;
var isMoving = false;
var pan = {
x: 0.0,
y: 0.0
};
var target = {
x: 0.0,
y: 0.0,
dist: 0.0
};
var ship = {
x: (canvasWidth * 0.5)|0,
y: (canvasHeight * 0.5)|0,
width: 10.0,
height: 10.0,
rotation: 0.0,
deltaRotation: 0.0
};
var stars = [];
stars.length = 100;
window.onload = function() {
canvas = document.getElementById("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
ctx = canvas.getContext("2d");
bounds = canvas.getBoundingClientRect();
for (var i = 0; i < stars.length; ++i) {
stars[i] = {
x: (Math.random() * 4 * canvasWidth - 2 * canvasWidth)|0,
y: (Math.random() * 4 * canvasHeight - 2 * canvasHeight)|0
};
}
loop();
}
window.onmousedown = function(e) {
isRotating = true;
isMoving = false;
target.x = pan.x + e.clientX - bounds.left;
target.y = pan.y + e.clientY - bounds.top;
}
function loop() {
// Tick
// Rotate to face target
if (isRotating) {
// Vector creation
var tX = ship.x - target.x;
var tY = ship.y - target.y;
var tL = Math.sqrt(tX**2 + tY**2);
var fX = Math.sin(ship.rotation);
var fY = -Math.cos(ship.rotation);
var sX = Math.sin(ship.rotation + Math.PI * 0.5);
var sY = -Math.cos(ship.rotation + Math.PI * 0.5);
// Normalization
tX = tX / tL;
tY = tY / tL;
// Left or right?
var a = 1.0 - Math.abs(tX * fX + tY * fY);
ship.rotation += 0.075 * (sX * tX + sY * tY < 0.0 ? 1.0 : -1.0);
if (a < 0.01) {
target.dist = tL;
isRotating = false;
isMoving = true;
}
}
// Accelerate to target
if (isMoving) {
ship.x += Math.sin(ship.rotation) * 1.0;
ship.y -= Math.cos(ship.rotation) * 1.0;
if (--target.dist < 0.0) {
isMoving = false;
}
}
// Render
// Update pan coordinates
pan.x = ship.x - canvasWidth * 0.5;
pan.y = ship.y - canvasHeight * 0.5;
// Draw background
ctx.fillStyle = "#222222";
ctx.fillRect(0,0,canvasWidth,canvasHeight);
ctx.fillStyle = "white";
for (var i = 0; i < stars.length; ++i) {
ctx.fillRect(
stars[i].x - pan.x,
stars[i].y - pan.y,
2,
2
);
}
// Draw ship
ctx.strokeStyle = "white";
ctx.fillStyle = "darkred";
ctx.translate(canvasWidth * 0.5,canvasHeight * 0.5);
ctx.rotate(ship.rotation);
ctx.beginPath();
ctx.moveTo(-ship.width * 0.5,ship.height * 0.5);
ctx.lineTo(ship.width * 0.5,ship.height * 0.5);
ctx.lineTo(0.0,-ship.height * 0.5);
ctx.lineTo(-ship.width * 0.5,ship.height * 0.5);
ctx.fill();
ctx.stroke();
ctx.rotate(-ship.rotation);
ctx.translate(-canvasWidth * 0.5,-canvasHeight * 0.5);
requestAnimationFrame(loop);
}
</script>
</body>
</html>

Animated SVG circles (timer and progress)

How can I change this:
var el = document.getElementById('graph'); // get canvas
var options = {
percent: el.getAttribute('data-percent') || 25,
size: el.getAttribute('data-size') || 220,
lineWidth: el.getAttribute('data-line') || 15,
rotate: el.getAttribute('data-rotate') || 0
}
var canvas = document.createElement('canvas');
var span = document.createElement('span');
span.textContent = options.percent + '%';
if (typeof(G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}
var ctx = canvas.getContext('2d');
canvas.width = canvas.height = options.size;
el.appendChild(span);
el.appendChild(canvas);
ctx.translate(options.size / 2, options.size / 2); // change center
ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg
//imd = ctx.getImageData(0, 0, 240, 240);
var radius = (options.size - options.lineWidth) / 2;
var drawCircle = function(color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
};
drawCircle('#efefef', options.lineWidth, 100 / 100);
drawCircle('#555555', options.lineWidth, options.percent / 100);
div {
position:relative;
margin:80px;
width:220px; height:220px;
}
canvas {
display: block;
position:absolute;
top:0;
left:0;
}
span {
color:#555;
display:block;
line-height:220px;
text-align:center;
width:220px;
font-family:sans-serif;
font-size:40px;
font-weight:100;
margin-left:5px;
}
input {
width: 200px;
}
<div class="chart" id="graph" data-percent="10"></div>
To get this:
outer circle is just a progress from 0 to 100%
inner - timer
I did this
but through the ass
http://codepen.io/di3orlive/pen/wKjBzY
I would like to do it more nicer
and with an arrow
var el = document.getElementById('graph'); // get canvas
var options = {
percent: el.getAttribute('data-percent') || 25,
size: el.getAttribute('data-size') || 220,
lineWidth: el.getAttribute('data-line') || 4,
rotate: el.getAttribute('data-rotate') || 0
}
function Gauge() {
this.to_rad = to_rad = Math.PI / 180;
this.percentBg = "#efefef";
this.percentFg = "#555555";
this.tickFg = "#cccccc";
this.tickBg = "transparent";
this.tickFill = true;
this.tickDirection = -1; // 1=clockwise, -1=counterclockewise
this.percent = 0;
this.size = 220;
this.lineWidth = 14;
this.rotate = 0;
this.ticks = 40;
this.tick = 0;
this.can = document.createElement('canvas');
this.ctx = this.can.getContext('2d');
}
Gauge.prototype.drawCircle = function(color, lineWidth, percent, drawArrow) {
var ctx = this.ctx;
var circleMargin = 10; // need room for arrowhead
var radius = (this.size - this.lineWidth - circleMargin) / 2;
ctx.save();
var mid = this.size / 2;
ctx.translate(mid, mid);
ctx.rotate(-90 * to_rad);
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
var endRadians = 360 * percent * this.to_rad;
ctx.arc(0, 0, radius, 0, endRadians, false);
ctx.strokeStyle = ctx.filStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
if (drawArrow === true && percent !== 1) {
ctx.beginPath();
ctx.rotate(endRadians);
var arrowWidth = this.lineWidth + 12;
var arrowHeight = 10;
ctx.moveTo(radius - (arrowWidth / 2), 0);
ctx.lineTo(radius + (arrowWidth / 2), 0);
ctx.lineTo(radius, arrowHeight);
ctx.fill();
}
ctx.restore();
};
Gauge.prototype.drawTicks = function() {
var circleMargin = 10; // need room for arrowhead
var radius = (this.size - this.lineWidth - circleMargin) / 2;
var ctx = this.ctx;
ctx.save();
var mid = this.size / 2;
ctx.translate(mid, mid);
ctx.rotate(-90 * to_rad);
ctx.lineWidth = 3;
var angle = 360 / this.ticks;
var tickSize = 20;
var tickMargin = 10;
for (var i = 0; i < this.ticks; i++) {
if ((i < this.tick && this.tickFill == true) || i == this.tick) {
ctx.strokeStyle = this.tickFg;
} else {
ctx.strokeStyle = this.tickBg;
}
ctx.save();
if (this.tickDirection === -1) {
ctx.rotate((360 - (i * angle)) * this.to_rad);
} else {
ctx.rotate(i * angle * this.to_rad);
}
ctx.beginPath();
ctx.moveTo(radius - tickSize - tickMargin, 0);
ctx.lineTo(radius - tickMargin, 0);
ctx.stroke();
ctx.restore();
}
ctx.restore();
};
Gauge.prototype.render = function(el) {
this.can.width = this.can.height = this.size;
this.span = document.createElement('span');
el.innerHTML = "";
el.appendChild(this.can);
el.appendChild(this.span);
var self = this;
var ctx = self.ctx;
function loop() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
self.drawCircle(self.percentBg, self.lineWidth, 100 / 100);
self.drawCircle(self.percentFg, self.lineWidth, self.percent / 100, true);
self.drawTicks();
self.timeout = setTimeout(function() {
loop()
}, 1000 / 30);
}
loop();
}
var myGauge = new Gauge();
myGauge.size = options.size;
myGauge.percent = options.percent;
myGauge.lineWidth = options.lineWidth;
myGauge.percent = options.percent;
myGauge.render(el)
var myGauge2 = new Gauge();
myGauge2.size = options.size;
myGauge2.percent = options.percent;
myGauge2.lineWidth = options.lineWidth;
myGauge2.percent = options.percent;
myGauge2.tickFg = "#FF8800";
myGauge2.tickBg = "#EEEEEE";
myGauge2.tickFill = false;
myGauge2.ticks = 60;
myGauge2.tickDirection = 1;
myGauge2.render(document.getElementById('gauge'));
var startTime = (new Date()).getTime();
var countDown = 99;
function dataLoop() {
myGauge.percent = myGauge.percent > 100 ? 100 : (myGauge.percent * 1) + .1;
var elapsedMs = (new Date().getTime()) - startTime; // milliseconds;
var elapsedSec = elapsedMs / 1000;
var remainSec = Math.floor(countDown - elapsedSec);
var progress = remainSec <=0 ? 1 : elapsedSec / countDown;
myGauge.tick = Math.floor(progress * myGauge.ticks);
myGauge.span.innerHTML = remainSec > 0 ? remainSec + " sec" : "---";
var d = new Date();
myGauge2.percent = (d.getMinutes() / 60) * 100;
if (myGauge2.percent > 100) myGauge2.percent = 100;
myGauge2.tick = d.getSeconds();
myGauge2.span.innerHTML = d.getSeconds() + " sec";
setTimeout(dataLoop,1000/30);
}
dataLoop();
div {
position: relative;
margin: 80px;
width: 220px;
height: 220px;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
}
span {
color: #555;
display: block;
line-height: 220px;
text-align: center;
width: 100%;
font-family: sans-serif;
font-size: 40px;
font-weight: 100;
margin-left: 5px;
}
input {
width: 200px;
}
<table>
<tr>
<td>
<div class="chart" id="graph" data-percent="10"></div>
</td>
<td>
<div id="gauge"></div>
</td>
</tr>
</table>

Categories

Resources