I have a canvas animation which you can see here.
I've noticed that once you have watched the animation after a while (approximately 25 seconds) the animation starts to jump around. I'm struggling to figure out how to make it so that it is one constant fluid motion?
Code below:
<canvas id="canvas"></canvas>
var canvas = document.querySelector("#canvas");
var ctx = canvas.getContext("2d");
canvas.width = parseInt(getComputedStyle(canvas).width);
canvas.height = parseInt(getComputedStyle(canvas).height);
var P = 4;
var A = 4;
function draw(shift) {
var w = canvas.width;
var h = canvas.height;
shift = shift >= 500*Math.PI ? shift - 100*Math.PI : shift;
ctx.clearRect(0, 0, w, h);
var grd = ctx.createLinearGradient(0, 0, w, h);
grd.addColorStop(0, "#4a8bf5");
grd.addColorStop(1, "#f16b55");
ctx.strokeStyle = grd;
ctx.lineCap = "round";
for (var i = 0; i < w; ) {
var _A = Math.abs(A*Math.cos(2*i));
ctx.beginPath();
var pos = Math.exp(-_A * i / w) * Math.sin(P * Math.PI * (i + shift) / w);
pos *= h / 2;
var lw = Math.exp(-_A * i / w) * Math.sin(3 * Math.PI * (i - shift) / w) * 2;
ctx.lineWidth = (lw)+1;
ctx.lineTo(i, h / 2 - pos);
ctx.closePath();
ctx.stroke();
i += 1;
}
window.requestAnimationFrame(function(){
draw(shift + 1);
});
}
draw(0);
The solution is to make sure that the change in shift results in the sin() argument changing by a multiple of 2π.
Given
Math.sin((i + shift) / (w / P))
this can be done using something like
if (shift > 500) shift -= 2 * Math.PI * (w / P);
There will still be a jump in the 2nd sin() argument here, the line width. To avoid this, shift has to be reduced by a number that causes both arguments to change by multiples of 2π, the LCM if you will.
var canvas = document.querySelector("#canvas");
var ctx = canvas.getContext("2d");
canvas.width = parseInt(getComputedStyle(canvas).width);
canvas.height = parseInt(getComputedStyle(canvas).height);
var P = 10;
var A = 4;
var shift = 0;
function draw() {
var w = canvas.width;
var h = canvas.height;
shift += 1;
if (shift > 500) shift -= 2 * Math.PI * (w / P);
shift_el.innerHTML = shift;
ctx.clearRect(0, 0, w, h);
var grd = ctx.createLinearGradient(0, 0, w, h);
grd.addColorStop(0, "#4a8bf5");
grd.addColorStop(1, "#f16b55");
ctx.strokeStyle = grd;
ctx.lineCap = "round";
for (var i = 0; i < w;) {
var _A = Math.abs(A * Math.cos(2 * i));
ctx.beginPath();
var pos = Math.exp(-_A * i / w) * Math.sin((i + shift) / (w / P));
pos *= h / 2;
var lw = Math.exp(-_A * i / w) * Math.sin(3 * Math.PI * (i - shift) / w) * 2;
ctx.lineWidth = (lw) + 1;
ctx.lineTo(i, h / 2 - pos);
ctx.closePath();
ctx.stroke();
i += 1;
}
window.requestAnimationFrame(draw);
}
draw();
body {
background: #ffffff;
padding: 0;
margin: 0;
}
canvas {
height: 200px;
width: 100%;
}
#shift_el {
position: absolute;
top: 0
}
<canvas id="canvas"></canvas>
<div id="shift_el"></div>
Related
So I have made some waves on a canvas. Now I would also like to add a background to make like a nice sunset with waves. But my problem occurs when I make ctx.fillRect to draw the background. As I need to clear the area around the bottom for the waves to work, the whole screen is being cleared. Therefore also clearing the background
whole code
var c = document.getElementById("screen2");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = window.innerWidth;
var h = 100;
var amplitude = h;
var frequency = .01;
var phi = 0;
var frames = 0;
var stopped = true;
var gradientSky = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientSky.addColorStop(0, "#C1274E");
gradientSky.addColorStop(0.25, "#C344B7");
gradientSky.addColorStop(0.5, "#B2244A");
gradientSky.addColorStop(0.75, "#B2244A");
gradientSky.addColorStop(1, "#B2244A");
ctx.fillStyle = gradientSky;
ctx.fillRect(0, 0, cw, ch);
var gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#7a88d9");
gradientA.addColorStop(1, "#5a6bd0");
var gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#646593");
gradientB.addColorStop(1, "#0f2460");
ctx.lineWidth = 4;
var step = 0;
function drawWaves() {
frames++;
phi = frames / 88;
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 390 + Math.sin(step / 2) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientA;
ctx.fill();
frames++;
phi = frames / 60;
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 4 + amplitude / 4;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 380 + Math.sin(step) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientB;
ctx.fill();
step += 0.02;
requestId = window.requestAnimationFrame(drawWaves);
}
requestId = window.requestAnimationFrame(drawWaves);
I'm pretty sure that has to do what x and y coordinates I inserted into the ctx.fillRect(); and ctx.clearRect();. But I have tried all variants I could think of but still nothing works. Sometimes I get the Background to appear but then the clear tag wont clear the waves properly. Just for better understanding the ch and cw are window.innerHeight and window.innerWidth although this can also be seen in the code.
The trick here is really simple. As most background images, your sunset also should fill the whole screen and will later be covered by the waves and ultimately the bubbles. This is how a painter would draw a painting. Since the background covers the whole canvas, you even don't need to clear it before because this is essentially being done by filling the canvas with the background.
So all you need to change is remove the clearReact() call and replace it by a fillRect(0,0,cw,ch) after setting the fillStyle to gradientSky.
Here's your modified example:
// Author:
// Name:
// URL: https://jsfiddle.net/7z2yh3pg/
function Blasen() {
const section = document.querySelector('#screen')
const createElement = document.createElement('spawn')
var size = Math.random() * 60;
createElement.style.width = 30 + size + 'px';
createElement.style.height = 30 + size + 'px';
createElement.style.left = Math.random() * innerWidth + "px";
section.appendChild(createElement);
setTimeout(() => {
createElement.remove()
}, 600)
}
const Blaseninterval = setInterval(Blasen, 100)
var c = document.getElementById("screen2");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = window.innerWidth;
var h = 100;
var amplitude = h;
var frequency = .01;
var phi = 0;
var frames = 0;
var stopped = true;
var gradientSky = ctx.createLinearGradient(0, 0, 0, ch);
gradientSky.addColorStop(0, "#C1274E");
gradientSky.addColorStop(0.25, "#C344B7");
gradientSky.addColorStop(0.5, "#B2244A");
gradientSky.addColorStop(0.75, "#B2244A");
gradientSky.addColorStop(1, "#B2244A");
ctx.fillStyle = gradientSky;
ctx.fill();
var gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#7a88d9");
gradientA.addColorStop(1, "#5a6bd0");
var gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#646593");
gradientB.addColorStop(1, "#0f2460");
ctx.lineWidth = 4;
var step = 0;
function drawWaves() {
frames++;
phi = frames / 88;
ctx.fillStyle = gradientSky;
ctx.beginPath();
ctx.rect(0, 0, cw, ch);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 250 + Math.sin(step / 2) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientA;
ctx.fill();
frames++;
phi = frames / 60;
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 4 + amplitude / 4;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 240 + Math.sin(step) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientB;
ctx.fill();
step += 0.02;
requestId = window.requestAnimationFrame(drawWaves);
}
requestId = window.requestAnimationFrame(drawWaves);
body,
html {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
display: block;
margin: 0 auto;
width: 100%;
height: 100vh;
}
#screen {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#screen spawn {
position: absolute;
bottom: -80px;
background: transparent;
border-radius: 50%;
pointer-events: none;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
animation: animate 3s linear infinite;
}
#screen spawn:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
opacity: 0.6;
border-radius: 50%;
}
#keyframes animate {
0% {
transform: translateY(0%);
opacity: 1;
}
99% {
opacity: 1;
}
100% {
transform: translateY(-2000%);
opacity: 0;
}
}
<canvas id="screen2"></canvas>
<div id="screen"></div>
So I have wanted to make an ocean in the night with a canvas. I got the Wave part down but I want them to be filled. So that they are not just a lign anymore but a solid block locking like waves. I really like how they look right now but if you have any tweeks that could make them look even better feel free to share them!
Thanks for the Help in Advance!
var c = document.getElementById("screen2");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = window.innerWidth;
var h = 100;
var amplitude = h;
var frequency = .01;
var phi = 0;
var frames = 0;
var stopped = true;
//ctx.strokeStyle = "Cornsilk";
ctx.lineWidth = 4;
//first wave
function Draw() {
frames++
phi = frames / 88;
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude/2 + amplitude /2;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch -110); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.stroke();
requestId = window.requestAnimationFrame(Draw);
}
requestId = window.requestAnimationFrame(Draw);
//second wave
function Draw2() {
frames++
phi = frames / 72 ;
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude/4 + amplitude/4;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch -110); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.stroke();
requestId = window.requestAnimationFrame(Draw2);
}
requestId = window.requestAnimationFrame(Draw2);
canvas {
display:block;
margin:0 auto;
width:100%;
height:100vh;
}
<canvas id="screen2"></canvas>
First and foremost what I really would change is using requestAnimationFrame two times as it's completely redundant. You can group your drawing operations in a single function and just execute this.
As #mousetail hinted a simple 'fix' would be using .fill() instead of .stroke(). The problem is that you would have two completely solid shapes of the same colour making it look like there's just a single wave. So the next step would be adding two different colours. As that might still look a little boring I'd use two linear gradients - a lighter for the foreground and a darker for the background wave.
To make it look even more interesting I'd move the waves up & down over time.
var c = document.getElementById("screen2");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = window.innerWidth;
var h = 100;
var amplitude = h;
var frequency = .01;
var phi = 0;
var frames = 0;
var stopped = true;
var gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#29c3d3");
gradientA.addColorStop(1, "#15656e");
var gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#55dee5");
gradientB.addColorStop(1, "#399499");
ctx.lineWidth = 4;
var step = 0;
function drawWaves() {
frames++;
phi = frames / 88;
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 110 + Math.sin(step / 2) * 10); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientA;
ctx.fill();
frames++;
phi = frames / 72;
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 4 + amplitude / 4;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 110 + Math.sin(step) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientB;
ctx.fill();
step += 0.02;
requestId = window.requestAnimationFrame(drawWaves);
}
requestId = window.requestAnimationFrame(drawWaves);
canvas {
display: block;
margin: 0 auto;
width: 100%;
height: 100vh;
}
<canvas id="screen2"></canvas>
I'm having difficulties replicating the pyramid below on the canvas.
I'm struggling with the math portion on how to draw a new ball on each new line. Here is my code so far.
<canvas id="testCanvas" width="300" height="300" style="border:1px solid #d3d3d3;"></canvas>
<script>
// Access canvas element and its context
const canvas = document.getElementById('testCanvas');
const context = canvas.getContext("2d");
const x = canvas.width;
const y = canvas.height;
const radius = 10;
const diamater = radius * 2;
const numOfRows = canvas.width / diamater;
function ball(x, y) {
context.arc(x, y, radius, 0, 2 * Math.PI, true);
context.fillStyle = "#FF0000"; // red
context.fill();
}
function draw() {
for (let i = 0; i < numOfRows; i++) {
for (let j = 0; j < i + 1; j++) {
ball(
//Pos X
(x / 2),
//Pos Y
diamater * (i + 1)
);
}
}
ball(x / 2, y);
context.restore();
}
draw();
</script>
I've been stuck on this problem for a while. I appreciate any assistance you can provide.
Thank you.
I noticed that the circle do not touch. I am not sure if you need or want them to but as this presented an interesting problem I create this answer.
Distance between stacked circles.
The distance between rows can be calculated using the right triangle as shown in the following image
Where R is the radius of the circle and D is the distance between rows.
D = ((R + R) ** 2 - R ** 2) ** 0.5;
With that we can get the number of rows we can fit given a radius as
S = (H - R * 2) / D;
Where H is the height of the canvas and S is the number of rows.
Example
Given a radius fits as many rows as possible into the give canvas height.
const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height, CENTER = W / 2;
const cols = ["#E80", "#0B0"];
draw();
function fillPath(path, x, y, color) {
ctx.fillStyle = color;
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.fill(path);
}
function draw() {
const R = 10;
const D = ((R * 2) ** 2 - R ** 2) ** 0.5;
const S = (H - R * 2) / D | 0;
const TOP = R + (H - (R * 2 + D * S)) / 2; // center horizontal
const circle = new Path2D();
circle.arc(0, 0, R, 0, Math.PI * 2);
var y = 0, x;
while (y <= S) {
x = 0;
const LEFT = CENTER - (y * R);
while (x <= y) {
fillPath(circle, LEFT + (x++) * R * 2, TOP + y * D, cols[y % 2]);
}
y ++;
}
}
canvas {
border:1px solid #ddd;
}
<canvas id="canvas" width="300" height="180"></canvas>
Radius to fit n rows of stacked circles
Or if you have the height H and the number of rows S you want to fit. As shown in next image.
We want to find R given H and S we rearrange for H and solve the resulting quadratic with
ss = S * S - 2 * S + 1;
a = 4 / ss;
b = -4 * H / ss;
c = H * H / ss;
R = (-b-(b*b - 4 * a * c) ** 0.5) / (2 * a); // the radius
Example
Given the number of rows (number input) calculates the radius that will fit that number of rows
const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height, CENTER = W / 2;
rowsIn.addEventListener("input", draw)
const cols = ["#DD0", "#0A0"];
draw();
function fillPath(path, x, y, color) {
ctx.fillStyle = color;
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.fill(path);
}
function draw() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0,0,W,H);
const S = Number(rowsIn.value);
const ss = S * S - 2 * S + 1;
const a = 4 / ss - 3, b = -4 * H / ss, c = H * H / ss;
const R = (- b - ((b * b - 4 * a * c) ** 0.5)) / (2 * a); // the radius
const TOP = R;
const D = ((R * 2) ** 2 - R ** 2) ** 0.5;
//const S = (H - R * 2) / D;
const circle = new Path2D();
circle.arc(0, 0, R, 0, Math.PI * 2);
var y = 0, x;
while (y < S) {
x = 0;
const LEFT = CENTER - (y * R);
while (x <= y) {
fillPath(circle, LEFT + (x++) * R * 2, TOP + y * D, cols[y % 2]);
}
y ++;
}
}
canvas {
border:1px solid #ddd;
}
<canvas id="canvas" width="300" height="180"></canvas>
<input type="number" id="rowsIn" min="3" max="12" value="3">Rows
How you can approach this problem is by breaking it down into one step at a time.
On (1)st row draw 1 circle
On (2)nd row draw 2 circles
On (3)rd row draw 3 circles
And so on...
Then you have to figure out where to draw each circle. That also you can break down into steps.
1st-row 1st circle in the center (width)
2nd-row 1st circle in the center minus diameter
2nd-row 2nd circle in the center plus diameter
and so on.
Doing this way you will find a pattern to convert into 2 for loops.
Something like this:
//1st row 1st circle
ball(w/2,radius * 1, red);
//2nd row 1st circle
ball(w/2 - radius,radius * 3, blue);
//2nd row 2nd circle
ball(w/2 + radius,radius * 3, blue);
The code below shows each step how each ball is drawn. I have also done few corrections to take care of the numberOfRows.
const canvas = document.getElementById('testCanvas');
const context = canvas.getContext("2d");
const w = canvas.width;
const h = canvas.height;
const radius = 10;
const diamater = radius * 2;
const numOfRows = Math.min(h / diamater, w / diamater);
const red = "#FF0000";
const blue = "#0000FF";
var k = 1;
function ball(x, y, color) {
setTimeout(function() {
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, true);
context.fillStyle = color;
context.fill();
}, (k++) * 250);
}
for (var i = 1; i <= numOfRows; i++) {
for (var j = 1; j <= i; j++) {
var y = (i * radius * 2) - radius;
var x = (w / 2) - ((i * radius) + radius) + (j * diamater);
ball(x, y, i % 2 ? red : blue);
}
}
<canvas id="testCanvas"
width="300" height="180"
style="border:1px solid #d3d3d3;"></canvas>
this might be somewhat difficult but i wil still ask, so i made a starfield ,now what i want to do is to have my stars( a pair each) connected to eachother by a line ,now this line will expand as the stars move forward and disappear when the stars move out of the canvas .any help would be appreciated here this is difficult i have the logic but i seem unable to follow the correct way to implement it
function randomRange(minVal, maxVal) {
return Math.floor(Math.random() * (maxVal - minVal - 1)) + minVal;
}
function initStars() {
for (var i = 0; i < stars.length; i++) {
stars[i] = {
x: randomRange(-25, 25),
y: randomRange(-25, 25),
z: randomRange(1, MAX_DEPTH)
}
}
}
function degToRad(deg) {
radians = (deg * Math.PI / 180) - Math.PI / 2;
return radians;
}
function animate() {
var halfWidth = canvas.width / 2;
var halfHeight = canvas.height / 2;
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < stars.length; i++) {
stars[i].z -= 0.2;
if (stars[i].z <= 0) {
stars[i].x = randomRange(-25, 25);
stars[i].y = randomRange(-25, 25);
stars[i].z = MAX_DEPTH;
}
var k = 128.0 / stars[i].z;
var px = stars[i].x * k + halfWidth;
var py = stars[i].y * k + halfHeight;
if (px >= 0 && px <= 1500 && py >= 0 && py <= 1500) {
var size = (1 - stars[i].z / 32.0) * 5;
var shade = parseInt((1 - stars[i].z / 32.0) * 750);
ctx.fillStyle = "rgb(" + shade + "," + shade + "," + shade + ")";
ctx.beginPath();
ctx.arc(px, py, size, degToRad(0), degToRad(360));
ctx.fill();
}
}
}
function animate() {
var halfWidth = canvas.width / 2;
var halfHeight = canvas.height / 2;
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < stars.length; i++) {
stars[i].z -= 0.2;
if (stars[i].z <= 0) {
stars[i].x = randomRange(-25, 25);
stars[i].y = randomRange(-25, 25);
stars[i].z = MAX_DEPTH;
}
var k = 128.0 / stars[i].z;
var px = stars[i].x * k + halfWidth;
var py = stars[i].y * k + halfHeight;
if (px >= 0 && px <= 1500 && py >= 0 && py <= 1500) {
var size = (1 - stars[i].z / 32.0) * 5;
var shade = parseInt((1 - stars[i].z / 32.0) * 750);
ctx.fillStyle = "rgb(" + shade + "," + shade + "," + shade + ")";
ctx.beginPath();
ctx.arc(px, py, size, degToRad(0), degToRad(360));
ctx.fill();
}
}
}
<!DOCTYPE html5>
<html>
<head>
<title>stars</title>
<script src="convergis.js"></script>
<script>
MAX_DEPTH = 32;
var canvas, ctx;
var stars = new Array(500);
window.onload = function() {
canvas = document.getElementById("tutorial");
if( canvas && canvas.getContext ) {
ctx = canvas.getContext("2d");
initStars();
setInterval(animate,17);
}
}
</script>
</head>
<body>
<canvas id='tutorial' width='1500' height='1500'>
</canvas>
</body>
</html>
You could just say you want a lightspeed effect!
One way very cheap way to do it is to paint the background with some transparency. You can also render a set of points close together in order to make the illusion of the effect.
The good way to do it is shaders since they will allow you to add glow and some other nice image trickery that will make it look better. Here is a good example: https://www.shadertoy.com/view/Xdl3D2
Below I used the canvas api lineTo and even with a fixed line width, it's a pretty good final result.
var MAX_DEPTH = 64;
var LINELENGTH = 0.1;
var stars = new Array(500);
var canvas = document.getElementById("tutorial");
canvas.width = innerWidth;
canvas.height = innerHeight;
var ctx = canvas.getContext("2d");
initStars();
setInterval(animate,17);
function randomRange(minVal, maxVal) {
return Math.floor(Math.random() * (maxVal - minVal - 1)) + minVal;
}
function initStars() {
for (var i = 0; i < stars.length; i++) {
stars[i] = {
x: randomRange(-25, 25),
y: randomRange(-25, 25),
z: randomRange(1, MAX_DEPTH)
}
}
}
function degToRad(deg) {
radians = (deg * Math.PI / 180) - Math.PI / 2;
return radians;
}
function animate() {
var halfWidth = canvas.width / 2;
var halfHeight = canvas.height / 2;
ctx.fillStyle = "rgba(0,0,0,1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < stars.length; i++) {
stars[i].z -= 0.5;
if (stars[i].z <= 0) {
stars[i].x = randomRange(-25, 25);
stars[i].y = randomRange(-25, 25);
stars[i].z = MAX_DEPTH;
}
var k = 254.0 / stars[i].z;
var px = stars[i].x * k + halfWidth;
var py = stars[i].y * k + halfHeight;
if (px >= 0 && px <= 1500 && py >= 0 && py <= 1500) {
var size = (1 - stars[i].z / 32.0) * 2;
var shade = parseInt((1 - stars[i].z / 32.0) * 750);
ctx.strokeStyle = "rgb(" + shade + "," + shade + "," + shade + ")";
ctx.lineWidth = size;
ctx.beginPath();
ctx.moveTo(px,py);
var ox = size * (px - halfWidth) * LINELENGTH;
var oy = size * (py - halfHeight) * LINELENGTH;
ctx.lineTo(px + ox, py + oy);
ctx.stroke();
}
}
}
<canvas id='tutorial' width='1500' height='1500'></canvas>
I'm trying to create a loading circle from an image in HTML5 canvas.
Here's the result I expect when percentage is at 50%:
here's what I've done after a lot of tests: (the blue stroke is just to see the circle, it'll be removed after)
var img = new Image();
img.onload = draw;
img.src = "http://i.imgur.com/HVJBZ1L.png";
var canvas = document.getElementsByTagName("canvas")[0];
canvas.width = 500;
canvas.height = 500;
var ctx = canvas.getContext("2d");
function draw() {
ctx.globalAlpha = 0.5
ctx.drawImage(img, 0, 0);
ctx.globalAlpha = 1;
var X = 50;
var Y = 50;
var Radius = img.width / 2;
var end = 40;
var start = 0;
var PI2 = Math.PI * 2;
var quart = Math.PI / 2;
var pct = 50 / 100;
var extent = (end - start) * pct;
var current = (end - start) / 100 * PI2 * pct - quart;
var pattern = ctx.createPattern(img, 'no-repeat');
ctx.beginPath();
ctx.arc(X, Y, Radius, -quart, current);
ctx.closePath();
ctx.fillStyle=pattern;
ctx.fill();
ctx.strokeStyle = "blue";
ctx.stroke();
}
<canvas></canvas>
as you can see, the result here isn't as excepted
What is wrong?
First, you need to correctly calculate your center point:
var X = img.width / 2;
var Y = img.height / 2;
Then you need to circle back in counter-clockwise direction tracing the inner radius Radius - 17:
ctx.beginPath();
ctx.arc(X, Y, Radius, -quart, current);
ctx.arc(X, Y, Radius - 17, current, -quart, true);
ctx.closePath();
If you aren't interested in the stroke outline, you could move to the center first, and then arc:
ctx.beginPath();
ctx.moveTo(X, Y);
ctx.arc(X, Y, Radius, -quart, current);
ctx.closePath();
Example:
var img = new Image();
img.onload = draw;
img.src = "http://i.imgur.com/HVJBZ1L.png";
var canvas = document.getElementsByTagName("canvas")[0];
canvas.width = 500;
canvas.height = 500;
var ctx = canvas.getContext("2d");
function draw() {
ctx.globalAlpha = 0.5
ctx.drawImage(img, 0, 0);
ctx.globalAlpha = 1;
var X = img.width / 2;
var Y = img.height / 2;
var Radius = img.width / 2;
var end = 40;
var start = 0;
var PI2 = Math.PI * 2;
var quart = Math.PI / 2;
var pct = 50 / 100;
var extent = (end - start) * pct;
var current = (end - start) / 100 * PI2 * pct - quart;
var pattern = ctx.createPattern(img, 'no-repeat');
ctx.beginPath();
ctx.moveTo(X, Y);
ctx.arc(X, Y, Radius, -quart, current);
ctx.closePath();
ctx.fillStyle = pattern;
ctx.fill();
}
<canvas></canvas>
const img = new Image();
img.src = "http://i.imgur.com/HVJBZ1L.png";
img.onload = imageLoaded;
var W,H; // width and height when image ready
const ctx = canvas.getContext("2d");
// define the distance of the progress 0- 100
const min = 0;
const max = 100;
var pattern;
var radius;
// get pattern, radius canvas size from image ans start animation
function imageLoaded(){
requestAnimationFrame(mainLoop);
W = this.width;
H = this.height;
pattern = ctx.createPattern(this, 'no-repeat');
radius = W / 2;
canvas.width = W;
canvas.height = H;
}
// draw the background and forground images
// amount is the amount of progress. amount >= min amount <= max
function draw(amount) {
ctx.globalAlpha = 0.5
ctx.drawImage(img, 0, 0);
ctx.globalAlpha = 1;
ctx.fillStyle=pattern;
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.arc( // draw inside circle CCW
W/2,
H/2,
radius - 17,
((amount - min) / (max-min)) * Math.PI * 2 - Math.PI / 2,
-Math.PI / 2,
true
);
ctx.arc( // draw outer circle CW
W/2,
H/2,
radius,
-Math.PI / 2,
((amount - min) / (max-min)) * Math.PI * 2 - Math.PI / 2
);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// animate the thing.
function mainLoop(time){
ctx.clearRect(0,0,canvas.width,canvas.height);
draw((time / 50) % max);
requestAnimationFrame(mainLoop);
}
canvas { border : 2px solid black;}
<canvas id="canvas"></canvas>