Make a JavaScript canvas background transparent - javascript

Am trying to make the background of this Codepen transparent https://codepen.io/scorch/pen/BZjbmW. I would like to have the Swirls on the a different backgrounds, instead of the colored background that is on the stated Codepen.
I have tried add css code but that did not seem to do anything. I tried messing with the Canvas RGB and that did not seem to do anything either.
// create a canvas element
var canvas = document.createElement("canvas")
// attach element to DOM
document.getElementsByTagName("body")[0].appendChild(canvas)
// background color [r, g, b]
var bg = [20, 0, 30]
var wh = window.innerHeight
// get the canvas context (this is the part we draw to)
var ctx = canvas.getContext("2d")
function setup() {
// setup the canvas size to match the window
canvas.width = window.innerWidth
canvas.height = window.innerHeight
wh = window.innerWidth < window.innerHeight ? window.innerWidth : window.innerHeight
// set the 0,0 point to the middle of the canvas, this is not necessary but it can be handy
ctx.translate(canvas.width / 2, canvas.height / 2)
fill(bg, 1)
}
// fill entire canvas with a preset color
function fill(rgb, amt) {
ctx.beginPath(); // start path
ctx.rect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height) // set rectangle to be the same size as the window
ctx.fillStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${amt})` // use the rgb array/color for fill, and amt for opacity
ctx.fill() // do the drawing
}
function drawCircle(x, y, r, color) {
ctx.beginPath()
ctx.arc(x, y, r, 0, 2 * Math.PI)
ctx.fillStyle = color || 'white'
ctx.fill()
ctx.closePath()
}
function Particle() {
// initialize loopers with random trange and offset
this.loop1 = new Looper(500 + 200 * Math.random(), 860 * Math.random())
this.loop2 = new Looper(320 + 70 * Math.random(), 20 * Math.random())
this.loop3 = new Looper(120 + 20 * Math.random(), 140 * Math.random())
this.history = []
this.history_max = 40
// this.x = null
// this.y = null
this.offset = Math.random() // some color offset for the color
this.draw = function() {
// set x,y, radius, and color params
var x = this.loop1.sin * (wh / 4 - 10) + this.loop2.sin * (wh / 6 - 10) + this.loop3.sin * 60
var y = this.loop1.cos * (wh / 4 - 10) + this.loop2.cos * (wh / 6 - 10) + this.loop3.cos * 10
var r = 0.2 + 3 * this.loop3.sinNorm * this.loop3.cosNorm // set the radius
var c = `hsla(${280 + 60 * (this.loop3.cosNorm + this.offset) * this.loop2.sinNorm}, ${100}%, ${50 + 10 * this.loop3.sin}%, ${1})`
ctx.beginPath()
ctx.strokeStyle = c
ctx.lineCap = 'round'
ctx.lineWidth = r
var tx = x
var ty = y
for (var i = 0; i < Math.min(this.history_max * this.loop3.cosNorm, this.history.length); i++) {
ctx.moveTo(tx, ty)
tx = this.history[i][0]
ty = this.history[i][1]
ctx.lineTo(tx, ty)
}
ctx.stroke()
drawCircle(x, y, r * 2 + 3, c); // draw the circle
this.loop1.update() // update looper
this.loop2.update() // update looper
this.loop3.update() // update looper
this.history.unshift([x, y])
if (this.history.length > this.history_max) {
this.history.pop()
}
}
}
// initialize a set of particle
var particles = []
for (var i = 0; i < 90; i++) {
particles.push(new Particle())
}
function draw() {
// fill context with background color
fill(bg, 0.36)
// update all the particles
for (var i = 0; i < particles.length; i++) {
particles[i].draw() // do it once
}
// this is a draw loop, this will execute frequently and is comparable to EnterFrame on other platform
window.requestAnimationFrame(function() {
draw()
})
}
// start enterFrame loop
window.requestAnimationFrame(draw);
// force running setup
setup()
// re-setup canvas when the size of the window changes
window.addEventListener("resize", setup)
// create a class to hold value and have built in incrementing functionality
function Looper(steps, start) {
this.val = start || 0 // set value to start value if defined, or 1
this.steps = steps || 100 // set steps to passed value or default to 100
this.norm = this.val / this.range // initialize normalized value (between 0 and 1)
this.sin = Math.sin(this.norm * Math.PI * 2) // get sine value from norm normalized to [0, 2PI]
this.sinNorm = (this.sin + 1) / 2 // normalize sin to [0,1]
this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]
this.update = function() {
this.val = (this.val + 1) % this.steps // update value
this.norm = this.val / this.steps // update normalize value (between 0 and 1)
this.sin = Math.sin(this.norm * Math.PI * 2) // get sine value from norm normalized to [0, 2PI]
this.sinNorm = (this.sin + 1) / 2 // normalize sine to [0,1]
this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]
}
}

ctx.fillStyle = rgba(255,255,255,0)
// create a canvas element
var canvas = document.createElement("canvas")
// attach element to DOM
document.getElementsByTagName("body")[0].appendChild(canvas)
// background color [r, g, b]
var bg = [20, 0, 30]
var wh = window.innerHeight
// get the canvas context (this is the part we draw to)
var ctx = canvas.getContext("2d")
function setup() {
// setup the canvas size to match the window
canvas.width = window.innerWidth
canvas.height = window.innerHeight
wh = window.innerWidth < window.innerHeight ? window.innerWidth : window.innerHeight
// set the 0,0 point to the middle of the canvas, this is not necessary but it can be handy
ctx.translate(canvas.width / 2, canvas.height / 2)
fill(bg, 1)
}
// fill entire canvas with a preset color
function fill(rgb, amt) {
ctx.beginPath(); // start path
ctx.rect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height) // set rectangle to be the same size as the window
ctx.fillStyle = `rgba(255,255,255,0)` // use the rgb array/color for fill, and amt for opacity
ctx.fill() // do the drawing
}
function drawCircle(x, y, r, color) {
ctx.beginPath()
ctx.arc(x, y, r, 0, 2 * Math.PI)
ctx.fillStyle = color || 'white'
ctx.fill()
ctx.closePath()
}
function Particle() {
// initialize loopers with random trange and offset
this.loop1 = new Looper(500 + 200 * Math.random(), 860 * Math.random())
this.loop2 = new Looper(320 + 70 * Math.random(), 20 * Math.random())
this.loop3 = new Looper(120 + 20 * Math.random(), 140 * Math.random())
this.history = []
this.history_max = 40
// this.x = null
// this.y = null
this.offset = Math.random() // some color offset for the color
this.draw = function() {
// set x,y, radius, and color params
var x = this.loop1.sin * (wh / 4 - 10) + this.loop2.sin * (wh / 6 - 10) + this.loop3.sin * 60
var y = this.loop1.cos * (wh / 4 - 10) + this.loop2.cos * (wh / 6 - 10) + this.loop3.cos * 10
var r = 0.2 + 3 * this.loop3.sinNorm * this.loop3.cosNorm // set the radius
var c = `hsla(${280 + 60 * (this.loop3.cosNorm + this.offset) * this.loop2.sinNorm}, ${100}%, ${50 + 10 * this.loop3.sin}%, ${1})`
ctx.beginPath()
ctx.strokeStyle = c
ctx.lineCap = 'round'
ctx.lineWidth = r
var tx = x
var ty = y
for (var i = 0; i < Math.min(this.history_max * this.loop3.cosNorm, this.history.length); i++) {
ctx.moveTo(tx, ty)
tx = this.history[i][0]
ty = this.history[i][1]
ctx.lineTo(tx, ty)
}
ctx.stroke()
drawCircle(x, y, r * 2 + 3, c); // draw the circle
this.loop1.update() // update looper
this.loop2.update() // update looper
this.loop3.update() // update looper
this.history.unshift([x, y])
if (this.history.length > this.history_max) {
this.history.pop()
}
}
}
// initialize a set of particle
var particles = []
for (var i = 0; i < 90; i++) {
particles.push(new Particle())
}
function draw() {
// fill context with background color
fill(bg, 0.36)
// update all the particles
for (var i = 0; i < particles.length; i++) {
particles[i].draw() // do it once
}
// this is a draw loop, this will execute frequently and is comparable to EnterFrame on other platform
window.requestAnimationFrame(function() {
draw()
})
}
// start enterFrame loop
window.requestAnimationFrame(draw);
// force running setup
setup()
// re-setup canvas when the size of the window changes
window.addEventListener("resize", setup)
// create a class to hold value and have built in incrementing functionality
function Looper(steps, start) {
this.val = start || 0 // set value to start value if defined, or 1
this.steps = steps || 100 // set steps to passed value or default to 100
this.norm = this.val / this.range // initialize normalized value (between 0 and 1)
this.sin = Math.sin(this.norm * Math.PI * 2) // get sine value from norm normalized to [0, 2PI]
this.sinNorm = (this.sin + 1) / 2 // normalize sin to [0,1]
this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]
this.update = function() {
this.val = (this.val + 1) % this.steps // update value
this.norm = this.val / this.steps // update normalize value (between 0 and 1)
this.sin = Math.sin(this.norm * Math.PI * 2) // get sine value from norm normalized to [0, 2PI]
this.sinNorm = (this.sin + 1) / 2 // normalize sine to [0,1]
this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]
}
}

Related

Is there a way to initialized Canvas api?

I am now making Customize roulette, and players can input the text what they wanted.
when I click button, the renderRoulette function is work. And this is the inner context of renderRoulette function
rouletteCanvas.style.display = "block"; // the initial state of rouletteCanvas's display is 'none'
// #customize elements are the inputs of the text that users made.
let customize = document.querySelectorAll("#customize");
let len = customize.length;
const canvas = document.querySelector(".roulette-panel"); // I want to draw here.
let width = canvas.width;
let height = canvas.height;
const ctx = canvas.getContext("2d");
// ctx Initialization
ctx.clearRect(0, 0, width, height);
const devide = len;
const degree = 360;
const goalDegree = 270 + degree / devide;
for (let i = 0; i < devide; i++) {
let json = {
first: (degree / devide) * i,
last: (degree / devide) * (i + 1),
text: `${i + 1}`,
};
data.push(json);
}
// Draw a circular sector
data.forEach((item) => {
ctx.save();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "gray";
ctx.moveTo(width / 2, height / 2);
ctx.arc(
width / 2,
height / 2,
width / 2,
degreesToRadians(item.first),
degreesToRadians(item.last),
false
);
ctx.closePath();
ctx.stroke();
ctx.restore();
ctx.save();
let half = Math.abs(item.first - item.last) / 2;
let degg = item.first + half;
let xx = ((Math.cos(degreesToRadians(degg)) * width) / 2) * 0.7 + width / 2;
let yy =
((Math.sin(degreesToRadians(degg)) * width) / 2) * 0.7 + height / 2;
let minus = ctx.measureText(item.text).width / 2;
ctx.font = "bold 15px sans-serif";
ctx.fillText(item.text, xx - minus, yy);
ctx.restore();
});
Every time I click the button, I want to delete all the previously drawn circular sector shapes and redraw them, so I used the code, ctx.clearRect(0, 0, width, height); . However, even if you click the button after, it will be drawn over the existing picture. How do I initialize it?
I get a feeling that the save and restore in your code are causing some problems for you, I tried a simpler version of your code and the ctx.clearRect(0, 0, width, height) works just fine.
Here is the code:
const canvas = document.querySelector(".roulette-panel");
const ctx = canvas.getContext("2d");
let width = height = 100;
const devide = 8;
let data = []
for (let i = 0; i < devide; i++) {
data.push({
first: (360 / devide) * i,
last: (360 / devide) * (i + 1)
});
}
function draw() {
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
data.forEach((item) => {
ctx.moveTo(width / 2, height / 2);
ctx.arc(50, 50, 45,
item.first * Math.PI / 180,
item.last * Math.PI / 180
);
item.first += 0.5
item.last += 0.5
});
ctx.stroke();
}
setInterval(draw, 40)
<canvas class="roulette-panel"></canvas>
This code uses setInterval to delete all the previously drawn circular sector shapes and draw new one on a new position creating the effect of movement
Here is that same code but with the clearRect commented:
const canvas = document.querySelector(".roulette-panel");
const ctx = canvas.getContext("2d");
let width = height = 100;
const devide = 8;
let data = []
for (let i = 0; i < devide; i++) {
data.push({
first: (360 / devide) * i,
last: (360 / devide) * (i + 1)
});
}
function draw() {
//ctx.clearRect(0, 0, width, height);
ctx.beginPath();
data.forEach((item) => {
ctx.moveTo(width / 2, height / 2);
ctx.arc(50, 50, 45,
item.first * Math.PI / 180,
item.last * Math.PI / 180
);
item.first += 0.5
item.last += 0.5
});
ctx.stroke();
}
setInterval(draw, 40)
<canvas class="roulette-panel"></canvas>
We can clearly see a different effect

Build a pyramid of balls using the 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>

How to draw only visible part of the tilemap on js canvas?

I created simple tilemap using Tiled (3200 x 3200 pixels). I loaded it on my canvas using this library
I draw entire tilemap 3200 x 3200 60 times per seocnd.
I tried to move around and it works fine. Btw, I move around canvas using ctx.translate. I included this in my own function
But when I created bigger map in Tiled ( 32000 x 32000 pixels ) - I got a very freezing page. I couldn't move around fast, I think there was about 10 fps
So how to fix it? I have to call drawTiles() function 60 times per second. But is there any way to draw only visible part of the tile? Like draw only what I see on my screen (0, 0, monitorWidth, monitorHeight I guess)
Thank you
##Drawing a large tileset
If you have a large tile set and only see part of it in the canvas you just need to calculate the tile at the top left of the canvas and the number of tiles across and down that will fit the canvas.
Then draw the square array of tiles that fit the canvas.
In the example the tile set is 1024 by 1024 tiles (worldTileCount = 1024), each tile is 64 by 64 pixels tileSize = 64, making the total playfield 65536 pixels square
The position of the top left tile is set by the variables worldX, worldY
###Function to draw tiles
// val | 0 is the same as Math.floor(val)
var worldX = 512 * tileSize; // pixel position of playfield
var worldY = 512 * tileSize;
function drawWorld(){
const c = worldTileCount; // get the width of the tile array
const s = tileSize; // get the tile size in pixels
// get the tile position
const tx = worldX / s | 0; // get the top left tile
const ty = worldY / s | 0;
// get the number of tiles that will fit the canvas
const tW = (canvas.width / s | 0) + 2;
const tH = (canvas.height / s | 0) + 2;
// set the location. Must floor to pixel boundary or you get holes
ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);
// Draw the tiles across and down
for(var y = 0; y < tH; y += 1){
for(var x = 0; x < tW; x += 1){
// get the index into the tile array for the tile at x,y plus the topleft tile
const i = tx + x + (ty + y) * c;
// get the tile id from the tileMap. If outside map default to tile 6
const tindx = tileMap[i] === undefined ? 6 : tileMap[i];
// draw the tile at its location. last 2 args are x,y pixel location
imageTools.drawSpriteQuick(tileSet, tindx, (tx + x) * s, (ty + y) * s);
}
}
}
###setTransform and absolute coordinates.
Use absolute coordinates makes everything simple.
Use the canvas context setTransform to set the world position and then each tile can be drawn at its own coordinate.
// set the world location. The | 0 floors the values and ensures no holes
ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);
That way if you have a character at position 51023, 34256 you can just draw it at that location.
playerX = 51023;
playerY = 34256;
ctx.drawImage(myPlayerImage,playerX,playerY);
If you want the tile map relative to the player then just set the world position to be half the canvas size up and to the left plus one tile to ensure overlap
playerX = 51023;
playerY = 34256;
worldX = playerX - canvas.width / 2 - tileWidth;
worldY = playerY - canvas.height / 2 - tileHeight;
###Demo of large 65536 by 65536 pixel tile map.
At 60fps if you have the horses and can handle much much bigger without any frame rate loss. (map size limit using this method is approx 4,000,000,000 by 4,000,000,000pixels (32 bit integers coordinates))
#UPDATE 15/5/2019 re Jitter
The comments have pointed out that there is some jitter as the map scrolls.
I have made changes to smooth out the random path with a strong ease in out turn every 240 frame (4 seconds at 60fps) Also added a frame rate reducer, if you click and hold the mouse button on the canvas the frame rate will be slowed to 1/8th normal so that the jitter is easier to see.
There are two reasons for the jitter.
###Time error
The first and least is the time passed to the update function by requestAnimationFrame, the interval is not perfect and rounding errors due to the time is compounding the alignment problems.
To reduce the time error I have set the move speed to a constant interval to minimize the rounding error drift between frames.
###Aligning tiles to pixels
The main reason for the jitter is that the tiles must be rendered on pixel boundaries. If not then aliasing errors will create visible seams between tiles.
To see the difference click the button top left to toggle pixel alignment on and off.
To get smooth scrolling (sub pixel positioning) draw the map to an offscreen canvas aligning to the pixels, then render that canvas to the display canvas adding the sub pixel offset. That will give the best possible result using the canvas. For better you will need to use webGL
###End of update
var refereshSkip = false; // when true drops frame rate by 4
var dontAlignToPixel = false;
var ctx = canvas.getContext("2d");
function mouseEvent(e) {
if(e.type === "click") {
dontAlignToPixel = !dontAlignToPixel;
pixAlignInfo.textContent = dontAlignToPixel ? "Pixel Align is OFF" : "Pixel Align is ON";
} else {
refereshSkip = e.type === "mousedown";
}
}
pixAlignInfo.addEventListener("click",mouseEvent);
canvas.addEventListener("mousedown",mouseEvent);
canvas.addEventListener("mouseup",mouseEvent);
// wait for code under this to setup
setTimeout(() => {
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
// create tile map
const worldTileCount = 1024;
const tileMap = new Uint8Array(worldTileCount * worldTileCount);
// add random tiles
doFor(worldTileCount * worldTileCount, i => {
tileMap[i] = randI(1, tileCount);
});
// this is the movement direction of the map
var worldDir = Math.PI / 4;
/* =======================================================================
Drawing the tileMap
========================================================================*/
var worldX = 512 * tileSize;
var worldY = 512 * tileSize;
function drawWorld() {
const c = worldTileCount; // get the width of the tile array
const s = tileSize; // get the tile size in pixels
const tx = worldX / s | 0; // get the top left tile
const ty = worldY / s | 0;
const tW = (canvas.width / s | 0) + 2; // get the number of tiles to fit canvas
const tH = (canvas.height / s | 0) + 2;
// set the location
if(dontAlignToPixel) {
ctx.setTransform(1, 0, 0, 1, -worldX,-worldY);
} else {
ctx.setTransform(1, 0, 0, 1, Math.floor(-worldX),Math.floor(-worldY));
}
// Draw the tiles
for (var y = 0; y < tH; y += 1) {
for (var x = 0; x < tW; x += 1) {
const i = tx + x + (ty + y) * c;
const tindx = tileMap[i] === undefined ? 6 : tileMap[i];
imageTools.drawSpriteQuick(tileSet, tindx, (tx + x) * s, (ty + y) * s);
}
}
}
var timer = 0;
var refreshFrames = 0;
const dirChangeMax = 3.5;
const framesBetweenDirChange = 240;
var dirChangeDelay = 1;
var dirChange = 0;
var prevDir = worldDir;
const eCurve = (v, p = 2) => v < 0 ? 0 : v > 1 ? 1 : v ** p / (v ** p + (1 - v) ** p);
//==============================================================
// main render function
function update() {
refreshFrames ++;
if(!refereshSkip || (refereshSkip && refreshFrames % 8 === 0)){
timer += 1000 / 60;
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
} else {
ctx.clearRect(0, 0, w, h);
}
// Move the map
var speed = Math.sin(timer / 10000) * 8;
worldX += Math.cos(worldDir) * speed;
worldY += Math.sin(worldDir) * speed;
if(dirChangeDelay-- <= 0) {
dirChangeDelay = framesBetweenDirChange;
prevDir = worldDir = prevDir + dirChange;
dirChange = rand(-dirChangeMax , dirChangeMax);
}
worldDir = prevDir + (1-eCurve(dirChangeDelay / framesBetweenDirChange,3)) * dirChange;
// Draw the map
drawWorld();
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
}, 0);
/*===========================================================================
CODE FROM HERE DOWN UNRELATED TO THE ANSWER
===========================================================================*/
const imageTools = (function() {
// This interface is as is. No warenties no garenties, and NOT to be used comercialy
var workImg, workImg1, keep; // for internal use
keep = false;
var tools = {
canvas(width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage: function(width, height) {
var i = this.canvas(width, height);
i.ctx = i.getContext("2d");
return i;
},
drawSpriteQuick: function(image, spriteIndex, x, y) {
var w, h, spr;
spr = image.sprites[spriteIndex];
w = spr.w;
h = spr.h;
ctx.drawImage(image, spr.x, spr.y, w, h, x, y, w, h);
},
line(x1, y1, x2, y2) {
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
},
circle(x, y, r) {
ctx.moveTo(x + r, y);
ctx.arc(x, y, r, 0, Math.PI * 2);
},
};
return tools;
})();
const doFor = (count, cb) => {
var i = 0;
while (i < count && cb(i++) !== true);
}; // the ; after while loop is important don't remove
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const seededRandom = (() => {
var seed = 1;
return {
max: 2576436549074795,
reseed(s) {
seed = s
},
random() {
return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max
}
}
})();
const randSeed = (seed) => seededRandom.reseed(seed | 0);
const randSI = (min, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
const tileSize = 64;
const tileCount = 7;
function drawGrass(ctx, c1, c2, c3) {
const s = tileSize;
const gs = s / (8 * c3);
ctx.fillStyle = c1;
ctx.fillRect(0, 0, s, s);
ctx.strokeStyle = c2;
ctx.lineWidth = 2;
ctx.lineCap = "round";
ctx.beginPath();
doFor(s, i => {
const x = rand(-gs, s + gs);
const y = rand(-gs, s + gs);
const x1 = rand(x - gs, x + gs);
const y1 = rand(y - gs, y + gs);
imageTools.line(x, y, x1, y1);
imageTools.line(x + s, y, x1 + s, y1);
imageTools.line(x - s, y, x1 - s, y1);
imageTools.line(x, y + s, x1, y1 + s);
imageTools.line(x, y - s, x1, y1 - s);
})
ctx.stroke();
}
function drawTree(ctx, c1, c2, c3) {
const seed = Date.now();
const s = tileSize;
const gs = s / 2;
const gh = gs / 2;
ctx.fillStyle = c1;
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
ctx.save();
ctx.shadowColor = "rgba(0,0,0,0.5)";
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 8;
randSeed(seed);
ctx.beginPath();
doFor(18, i => {
const ss = 1 - i / 18;
imageTools.circle(randS(gs - gh * ss, gs + gh * ss), randS(gs - gh * ss, gs + gh * ss), randS(gh / 4, gh / 2));
})
ctx.stroke();
ctx.fill();
ctx.restore();
ctx.fillStyle = c2;
ctx.strokeStyle = c3;
ctx.lineWidth = 2;
ctx.save();
randSeed(seed);
ctx.beginPath();
doFor(18, i => {
const ss = 1 - i / 18;
imageTools.circle(randS(gs - gh * ss, gs + gh * ss) - 2, randS(gs - gh * ss, gs + gh * ss) - 2, randS(gh / 4, gh / 2) / 1.6);
})
ctx.stroke();
ctx.fill();
ctx.restore();
}
const tileRenders = [
(ctx) => {
drawGrass(ctx, "#4C4", "#4F4", 1)
},
(ctx) => {
drawGrass(ctx, "#644", "#844", 2)
},
(ctx) => {
tileRenders[0](ctx);
drawTree(ctx, "#480", "#8E0", "#7C0")
},
(ctx) => {
tileRenders[1](ctx);
drawTree(ctx, "#680", "#AE0", "#8C0")
},
(ctx) => {
drawGrass(ctx, "#008", "#00A", 4)
},
(ctx) => {
drawGrass(ctx, "#009", "#00C", 4)
},
(ctx) => {
drawGrass(ctx, "#00B", "#00D", 4)
},
]
const tileSet = imageTools.createImage(tileSize * tileCount, tileSize);
const ctxMain = ctx;
ctx = tileSet.ctx;
tileSet.sprites = [];
doFor(tileCount, i => {
x = i * tileSize;
ctx.save();
ctx.setTransform(1, 0, 0, 1, x, 0);
ctx.beginPath();
ctx.rect(0, 0, tileSize, tileSize);
ctx.clip()
if (tileRenders[i]) {
tileRenders[i](ctx)
}
tileSet.sprites.push({
x,
y: 0,
w: tileSize,
h: tileSize
});
ctx.restore();
});
ctx = ctxMain;
canvas {
position: absolute;
top: 0px;
left: 0px;
}
div {
position: absolute;
top: 8px;
left: 8px;
color: white;
}
#pixAlignInfo {
color: yellow;
cursor: pointer;
border: 2px solid green;
margin: 4px;
}
#pixAlignInfo:hover {
color: white;
background: #0008;
cursor: pointer;
}
body {
background: #49c;
}
<canvas id="canvas"></canvas>
<div>Hold left button to slow to 1/8th<br>
<span id="pixAlignInfo">Click this button to toggle pixel alignment. Alignment is ON</span></div>

Canvas perpendicular points to line

I'm using Konva library to draw some stuff on HTML5 canvas.
I have given 2 points from user interaction by mouse click:
var A={x:'',y:''};
var B={x:'',y:''};
1) How to draw line line this?
My question is:
1) How to get perpendicular lines on each interval?
2) How to get distance from A to B point?
3) How to get all points on line from A to B?
4) How to get red points?
You have not explained what your line is so I am assuming it is a sin wave (though the image looks like circles stuck together???)
As MBo has given the basics this is just applying it to the wavy line.
// normalize a vector
function normalize(vec){
var length = Math.sqrt(vec.x * vec.x + vec.y * vec.y);
vec.x /= length;
vec.y /= length;
return vec;
}
// creates a wavy line
function wavyLine(start, end, waves, amplitude){
return ({
start,
end,
waves,
amplitude,
update(){
if(this.vec === undefined){
this.vec = {};
this.norm = {};
}
this.vec.x = this.end.x - this.start.x;
this.vec.y = this.end.y - this.start.y;
this.length = Math.sqrt(this.vec.x * this.vec.x + this.vec.y * this.vec.y);
this.norm.x = this.vec.x / this.length;
this.norm.y = this.vec.y / this.length;
return this;
}
}).update();
}
// draws a wavy line
function drawWavyLine(line) {
var x, stepSize, i, y, phase, dist;
ctx.beginPath();
stepSize = ctx.lineWidth;
ctx.moveTo(line.start.x, line.start.y);
for (i = stepSize; i < line.length; i+= stepSize) {
x = line.start.x + line.norm.x * i; // get point i pixels from start
y = line.start.y + line.norm.y * i; // get point i pixels from start
phase = (i / (line.length / line.waves)) * Math.PI * 2; // get the wave phase at this point
dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
x -= line.norm.y * dist;
y += line.norm.x * dist;
ctx.lineTo(x, y);
}
phase = line.waves * Math.PI * 2; // get the wave phase at this point
dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
ctx.lineTo(line.end.x - line.norm.y * dist, line.end.y + line.norm.x * dist);
ctx.stroke();
}
// find the closest point on a wavy line to a point returns the pos on the wave, tangent and point on the linear line
function closestPointOnLine(point,line){
var x = point.x - line.start.x;
var y = point.y - line.start.y;
// get the amount the line vec needs to be scaled so tat point is perpendicular to the line
var l = (line.vec.x * x + line.vec.y * y) / (line.length * line.length);
x = line.vec.x * l; // scale the vec
y = line.vec.y * l;
return pointAtDistance(Math.sqrt(x * x + y * y), line);
}
// find the point at (linear) distance along wavy line and return coordinate, coordinate on wave, and tangent
function pointAtDistance(distance,line){
var lenScale = line.length / line.waves; // scales the length into radians
var phase = distance * Math.PI * 2 / lenScale; // get the wave phase at this point
var dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
var slope = Math.cos(phase ) * Math.PI * 2 * line.amplitude / lenScale; // derivitive of sin(a*x) is -a*cos(a*x)
// transform tangent (slope) into a vector along the line. This vector is not a unit vector so normalize it
var tangent = normalize({
x : line.norm.x - line.norm.y * slope,
y : line.norm.y + line.norm.x * slope
});
// move from the line start to the point on the linear line at distance
var linear = {
x : line.start.x + line.norm.x * distance,
y : line.start.y + line.norm.y * distance
}
// move out perpendicular to the wavy part
return {
x : linear.x - line.norm.y * dist,
y : linear.y + line.norm.x * dist,
tangent,linear
};
}
// create a wavy line
var wLine = wavyLine({x:10,y:100},{x:300,y:100},3,50);
// draw the wavy line and show some points on it
function display(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
var radius = Math.max(ch,cw);
// set up the wavy line
wLine.waves = Math.sin(timer / 10000) * 6;
wLine.start.x = Math.cos(timer / 50000) * radius + cw;
wLine.start.y = Math.sin(timer / 50000) * radius + ch;
wLine.end.x = -Math.cos(timer / 50000) * radius + cw;
wLine.end.y = -Math.sin(timer / 50000) * radius + ch ;
wLine.update();
// draw the linear line
ctx.lineWidth = 0.5;
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.moveTo(wLine.start.x, wLine.start.y);
ctx.lineTo(wLine.end.x, wLine.end.y);
ctx.stroke();
// draw the wavy line
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
drawWavyLine(wLine);
// find point nearest mouse
var p = closestPointOnLine(mouse,wLine);
ctx.lineWidth = 1;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.arc(p.x,p.y,5,0,Math.PI * 2);
ctx.moveTo(p.x + p.tangent.x * 20,p.y + p.tangent.y * 20);
ctx.lineTo(p.x - p.tangent.y * 10,p.y + p.tangent.x * 10);
ctx.lineTo(p.x + p.tangent.y * 10,p.y - p.tangent.x * 10);
ctx.closePath();
ctx.stroke();
// find points at equal distance along line
ctx.lineWidth = 1;
ctx.strokeStyle = "blue";
ctx.beginPath();
for(var i = 0; i < w; i += w / 10){
var p = pointAtDistance(i,wLine);
ctx.moveTo(p.x + 5,p.y);
ctx.arc(p.x,p.y,5,0,Math.PI * 2);
ctx.moveTo(p.x,p.y);
ctx.lineTo(p.linear.x,p.linear.y);
ctx.moveTo(p.x + p.tangent.x * 40, p.y + p.tangent.y * 40);
ctx.lineTo(p.x - p.tangent.x * 40, p.y - p.tangent.y * 40);
}
ctx.stroke();
}
/******************************************************************************
The code from here down is generic full page mouse and canvas boiler plate
code. As I do many examples which all require the same mouse and canvas
functionality I have created this code to keep a consistent interface. The
Code may or may not be part of the answer.
This code may or may not have ES6 only sections so will require a transpiler
such as babel.js to run on legacy browsers.
*****************************************************************************/
// V2.0 ES6 version for Stackoverflow and Groover QuickRun
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0;
// You can declare onResize (Note the capital R) as a callback that is also
// called once at start up. Warning on first call canvas may not be at full
// size.
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var resizeTimeoutHandle;
var firstRun = true;
function createCanvas () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
function resizeCanvas () {
if (canvas === undefined) { canvas = createCanvas() }
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals() }
if (typeof onResize === "function") {
clearTimeout(resizeTimeoutHandle);
if (firstRun) { onResize() }
else { resizeTimeoutHandle = setTimeout(onResize, RESIZE_DEBOUNCE_TIME) }
firstRun = false;
}
}
function setGlobals () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
var m; // alias for mouse
var mouse = {
x : 0, y : 0, // mouse position and wheel
buttonRaw : 0,
buttonOnMasks : [0b1, 0b10, 0b100], // mouse button on masks
buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
bounds : null,
eventNames : "mousemove,mousedown,mouseup".split(","),
event(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
if (t === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
else if (t === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
},
start(element) {
m.element = element === undefined ? document : element;
m.eventNames.forEach(name => document.addEventListener(name, mouse.event) );
},
}
m = mouse;
return mouse;
})();
function update(timer) { // Main update loop
globalTime = timer;
display(timer); // call demo code
requestAnimationFrame(update);
}
setTimeout(function(){
canvas = createCanvas();
mouse.start(canvas);
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
We have points A and B. Difference vector
D.X = B.X - A.X
D.Y = B.Y - A.Y
Length = Sqrt(D.X * D.X + D.Y * D.Y)
normalized (unit) vector
uD.X = D.X / Length
uD.Y = D.Y / Length
perpendicular unit vector
P.X = - uD.Y
P.Y = uD.X
some red point:
R.X = A.X + uD.X * Dist + P.X * SideDist * SideSign
R.Y = A.Y + uD.Y * Dist + P.Y * SideDist * SideSign
where Dist is in range 0..Length
Dist = i / N * Length for N equidistant points
SideSign is +/- 1 for left and right side

Editing javascript canvas animation

I have a little trouble. I would like to use animation from:
http://codepen.io/chuckeles/pen/mJeaNJ
The main question, how to edit it, so instead of dots, there would be small image? In which part it should be changed? I have no idea where to start so I need a little guidance, in which part I should edit code to change dots to image. Any help or advice would be appreciated.
Full Javascript code here:
// --- CANVAS ---
var canvas = document.getElementById("bg");
var ctx = canvas.getContext("2d");
// --- UTILS ---
// request frame
var requestFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// window resizing
(window.onresize = function() {
// set new canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
})();
// --- STARS ---
var Star = function(x, y) {
this.x = x || 0;
this.y = y || 0;
this.mx = 0;
this.my = 0;
this.distance = 0;
this.mult = Math.random() * 0.4 + 0.6;
};
// --- GLOBALS ---
// the star array
var stars = [];
// stars to remove from the array
var starsToRemove = [];
// create random stars
for (var i = 0; i < 60; ++i) {
// pos
var x = Math.random() * canvas.width;
var y = Math.random() * canvas.height;
// create
stars.push(new Star(x, y));
}
// --- SETUP ---
// disable smoothing
ctx.imageSmoothingEnabled = false;
// --- MAIN LOOP ---
// loop function
var loop = function() {
// --- UPDATE ---
// create random stars
var chance = 0.2;
var asp = canvas.width / canvas.height;
if (Math.random() < chance)
stars.push(
new Star(0, Math.random() * canvas.height) // left
);
if (Math.random() < chance)
stars.push(
new Star(canvas.width, Math.random() * canvas.height) // right
);
if (Math.random() < chance * asp)
stars.push(
new Star(Math.random() * canvas.width, 0) // top
);
if (Math.random() < chance * asp)
stars.push(
new Star(Math.random() * canvas.width, canvas.height) // botton
);
// update stars
stars.forEach(function(star) {
// update motion
star.mx = -(star.x - canvas.width / 2) / 300;
star.my = -(star.y - canvas.height / 2) / 300;
// apply motion
star.x += star.mx * star.mult;
star.y += star.my * star.mult;
// update distance
star.distance = Math.sqrt(
Math.pow((star.x - canvas.width / 2), 2) +
Math.pow((star.y - canvas.height / 2), 2)
);
// remove if close to the center
if (star.distance < 40 * star.mult)
starsToRemove.push(star);
});
// remove stars
starsToRemove.forEach(function(toRemove) {
stars.splice(stars.indexOf(toRemove), 1);
});
starsToRemove = [];
// --- DRAW ---
// clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
// get image data
var data = ctx.getImageData(0, 0, canvas.width, canvas.height);
// for each star
stars.forEach(function(star) {
// get pos
var x = Math.floor(star.x);
var y = Math.floor(star.y);
// draw a pixel
var i = y * data.width + x;
data.data[i * 4 + 0] = 255;
data.data[i * 4 + 1] = 255;
data.data[i * 4 + 2] = 255;
// apply alpha based on distance
var a = (star.distance - 40 * star.mult) / (canvas.width / 2 - 40 * star.mult);
data.data[i * 4 + 3] = 255 * a * star.mult;
});
// put back
ctx.putImageData(data, 0, 0);
// new loop
requestFrame(loop);
};
// start loop
requestFrame(loop);
You might want to edit it after the comment:
// --- DRAW ---
There will be canvas drawing methods you might want to change. For example, the line
ctx.clearRect(0, 0, canvas.width, canvas.height);
might clear all pixels drawn in the size of canvas.
Mights return a array from canvas pixels on its size.
ctx.getImageData(0, 0, canvas.width, canvas.height);
Mights draw each pixel (star).
// for each star
stars.forEach(function(star) {
// get pos
var x = Math.floor(star.x);
var y = Math.floor(star.y);
// draw a pixel
var i = y * data.width + x;
// change pixel color
data.data[i * 4 + 0] = 255;//Red = 255
data.data[i * 4 + 1] = 255;//Green = 255
data.data[i * 4 + 2] = 255;//Blue = 255
// ^ color (rgb)
// apply alpha based on distance
var a = (star.distance - 40 * star.mult) / (canvas.width / 2 - 40 * star.mult);
data.data[i * 4 + 3] = 255 * a * star.mult;
});
// put back
ctx.putImageData(data, 0, 0);
// ^ this line update the canvas pixels edited as above
With DOM methods, you can draw span or div instead of pixels in canvas, but it's not good for performance do that.

Categories