HTML5 canvas for loop wont draw the first object in array - javascript

So basically. I'm trying to loop through each letter in a text (in this case the text is "marius"). Now, the problem is that the first letter is never drawn. If the text is "marius", it draws "arius". I've tried what I can think of, but I can't find the error. Does anyone know what I am doing wrong? Don't worry about anything else. The code is not done, but this problem if eating my brains out. Thanks in advance. :)
WebFont.load({
google: {
families: ['Audiowide']
},
active: function() {
// Just the requestAnimationFrame
// for different types of browsers
const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
// Canvas
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
const cWidth = c.width = window.innerWidth;
const cHeight = c.height = window.innerHeight;
// Framerate settings
// Better not touch theese if you
// do not know what you are doing
let now, delta;
let fps = (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) ? 29 : 60;
let then = Date.now();
const interval = 1000 / fps;
/*-------------------------------------------------------
PREPARATION BELOW
-------------------------------------------------------*/
var text = 'marius';
var letters = [];
var lettersCount;
const config = {
'background': '#222',
'letterSize': '72px',
'letterFont': 'Audiowide',
'letterSpacing': 50,
'amp': 60,
'yOffset': cHeight / 2
};
for (let i = 0, lettersCount = text.length; i < lettersCount; i++) {
letters.push(new Letter(text[i]));
}
for (let i = 0, len = letters.length; i < len; i++) {
var addThisToo = (i == 0) ? 0 : letters[i - 1].letterOffset;
var letterWidth = getTextWidth(letters[i].letter, config.letterSize + ' ' + config.letterFont);
letters[i].letterOffset = letterWidth + addThisToo;
}
/*-------------------------------------------------------
END PREPARATION
-------------------------------------------------------*/
/**
* draw()
* The draw function, where everything is happening
* #return null
*/
function draw() {
looper();
if (delta > interval) {
// Calculate then
then = now - (delta % interval);
// All your own stuff here
drawBackground();
drawLetters();
}
}
/**
* drawLetters()
* Draw the letters from letters Array
* Sinusoidal wave!
*/
function drawLetters() {
for (let i = 0, len = letters.length; i < len; i++) {
// Prepare X and Y of the letter
let letterOffset = (i > 0) ? letters[i - 1].letterOffset : letters[i].letterOffset;
let x = letters[i].xPos + letterOffset;
let y = config.yOffset + (sin(letters[i].xPos / 45 + i) * config.amp);
// Create gradient color
var gradient = ctx.createLinearGradient(0, 0, cWidth, 0);
gradient.addColorStop('0', '#ff6666');
gradient.addColorStop('0.5', '#66ff66');
gradient.addColorStop('1', '#6666ff');
// Draw and fill the letter
ctx.font = config.letterSize + ' ' + config.letterFont;
ctx.fillText(letters[i].letter, x, y);
ctx.fillStyle = gradient;
// Update letter X and Y position
letters[i].yPos += 0.05 * i;
letters[i].xPos -= letters[i].xVel;
}
}
/**
* letter(letter)
* Letter object
* #return nul
*/
function Letter(letter) {
this.letter = letter;
this.xPos = cWidth;
this.yPos = 0;
this.xVel = 2;
this.yVel = 0;
this.letterOffset = 0;
}
/**
* Looper()
* Looper function, do not touch!
* #return null
*/
function looper() {
requestAnimationFrame(draw);
now = Date.now();
delta = now - then;
}
/**
* drawBackground()
* Draws the background
* #return null
*/
function drawBackground() {
ctx.fillStyle = config.background;
ctx.fillRect(0, 0, c.width, c.height);
}
/**
* randInt(min, max)
* Returns random integer between min - max
* #param integer min
* #param integer max
* #return integer
*/
function randInt(min, max) {
max = max === undefined ? min - (min = 0) : max;
return Math.floor(Math.random() * (max - min) + min);
}
/**
* sin(x)
* Sinus of X
* #return float
*/
function sin(x) {
return Math.sin(x);
}
/**
* getTextWidth(text, font)
* Return the width of the text
* #return integer
*/
function getTextWidth(text, font) {
ctx.font = font;
var metrics = ctx.measureText(text);
return Math.round(metrics.width);
}
/**
* EventListener - Click
*/
document.addEventListener('click', function(e) {
let x, y;
if (e.offsetX) {
x = e.offsetX;
y = e.offsetY;
} else if (e.layerX) {
x = e.layerX;
y = e.layerY;
}
});
requestAnimationFrame(draw);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js"></script>
<canvas id="canvas"></canvas>

The problem in your implementation is that you set the ctx.fillStyle after you do the fillText call. This means that you only set the fillStyle of the second letter and as the default fillStyle is black the first will not be visible. If you switch the two lines it will work.
Then you have a second mistake which is that the first and the second letter are at the same x position. I changed the preparation method and the draw method to make the letter spacing work properly.
WebFont.load({
google: {
families: ['Audiowide']
},
active: function() {
// Just the requestAnimationFrame
// for different types of browsers
const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
// Canvas
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
const cWidth = c.width = window.innerWidth;
const cHeight = c.height = window.innerHeight;
// Framerate settings
// Better not touch theese if you
// do not know what you are doing
let now, delta;
let fps = (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) ? 29 : 60;
let then = Date.now();
const interval = 1000 / fps;
/*-------------------------------------------------------
PREPARATION BELOW
-------------------------------------------------------*/
var text = 'marius';
var letters = [];
var lettersCount;
const config = {
'background': '#222',
'letterSize': '72px',
'letterFont': 'Audiowide',
'letterSpacing': 50,
'amp': 60,
'yOffset': cHeight / 2
};
for (let i = 0, lettersCount = text.length; i < lettersCount; i++) {
letters.push(new Letter(text[i]));
}
for (let i = 1, len = letters.length; i < len; i++) {
var addThisToo = letters[i - 1].letterOffset;
var letterWidth = getTextWidth(letters[i - 1].letter, config.letterSize + ' ' + config.letterFont);
letters[i].letterOffset = letterWidth + addThisToo + config['letterSpacing'];
}
/*-------------------------------------------------------
END PREPARATION
-------------------------------------------------------*/
/**
* draw()
* The draw function, where everything is happening
* #return null
*/
function draw() {
looper();
if (delta > interval) {
// Calculate then
then = now - (delta % interval);
// All your own stuff here
drawBackground();
drawLetters();
}
}
/**
* drawLetters()
* Draw the letters from letters Array
* Sinusoidal wave!
*/
function drawLetters() {
for (let i = 0, len = letters.length; i < len; i++) {
// Prepare X and Y of the letter
let letterOffset = letters[i].letterOffset;
let x = letters[i].xPos + letterOffset;
let y = config.yOffset + (sin(letters[i].xPos / 45 + i) * config.amp);
// Create gradient color
var gradient = ctx.createLinearGradient(0, 0, cWidth, 0);
gradient.addColorStop('0', '#ff6666');
gradient.addColorStop('0.5', '#66ff66');
gradient.addColorStop('1', '#6666ff');
// Draw and fill the letter
ctx.font = config.letterSize + ' ' + config.letterFont;
ctx.fillStyle = gradient;
ctx.fillText(letters[i].letter, x, y);
// Update letter X and Y position
letters[i].yPos += 0.05 * i;
letters[i].xPos -= letters[i].xVel;
}
}
/**
* letter(letter)
* Letter object
* #return nul
*/
function Letter(letter) {
this.letter = letter;
this.xPos = cWidth;
this.yPos = 0;
this.xVel = 2;
this.yVel = 0;
this.letterOffset = 0;
}
/**
* Looper()
* Looper function, do not touch!
* #return null
*/
function looper() {
requestAnimationFrame(draw);
now = Date.now();
delta = now - then;
}
/**
* drawBackground()
* Draws the background
* #return null
*/
function drawBackground() {
ctx.fillStyle = config.background;
ctx.fillRect(0, 0, c.width, c.height);
}
/**
* randInt(min, max)
* Returns random integer between min - max
* #param integer min
* #param integer max
* #return integer
*/
function randInt(min, max) {
max = max === undefined ? min - (min = 0) : max;
return Math.floor(Math.random() * (max - min) + min);
}
/**
* sin(x)
* Sinus of X
* #return float
*/
function sin(x) {
return Math.sin(x);
}
/**
* getTextWidth(text, font)
* Return the width of the text
* #return integer
*/
function getTextWidth(text, font) {
ctx.font = font;
var metrics = ctx.measureText(text);
return Math.round(metrics.width);
}
/**
* EventListener - Click
*/
document.addEventListener('click', function(e) {
let x, y;
if (e.offsetX) {
x = e.offsetX;
y = e.offsetY;
} else if (e.layerX) {
x = e.layerX;
y = e.layerY;
}
});
requestAnimationFrame(draw);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js"></script>
<canvas id="canvas"></canvas>

Related

What can I do to help my canvas animation run faster in safari like it does in chrome?

I've got an animation that runs great the first few times on safari. But after each time the loop is triggered it slows down slightly. On chrome I don't experience the slow down. Is there some trick I'm needing to utilize for safari?
External link: Codepen
Here is my JS example:
let canvas = document.querySelector('.canvas');
let ctx = canvas.getContext('2d');
let scratch = document.createElement('canvas');
let ctxS = scratch.getContext('2d', { alpha: false });
let vw = window.innerWidth;
let vh = window.innerHeight;
let circleRadius = 50;
let circleSpacing = 3;
let stepDistanceX;
let stepDistanceY;
let originCircle;
let clickNum = 0;
let circles = [];
// Transition vars.
let frame;
let isZooming = false;
let destination;
let dx;
let dy;
let ds;
let dt = 0;
let zoomingImage;
// For matrix circles.
function setCircleSizes() {
if (vw < 600) {
circleRadius = 20;
circleSpacing = 2.5;
}
else if (vw < 900) {
circleRadius = 40;
circleSpacing = 3;
}
}
// Easing funciton for animation (linear)
function easing(t) {
return t
}
// On window resize.
function resize() {
canvas.width = vw;
canvas.height = vh;
scratch.width = Math.max(vw, vh);
scratch.height = Math.max(vw, vh);
}
// Set matrix for circles.
function setCircleMatrix() {
stepDistanceX = (circleRadius * circleSpacing) + ((vw % (circleRadius * circleSpacing)) / 2);
stepDistanceY = (circleRadius * circleSpacing) + ((vh % (circleRadius * circleSpacing)) / 2);
const circlesAcross = Math.floor(vw / stepDistanceX);
const circlesDown = Math.floor(vh / stepDistanceY);
let circlesToAdd = circlesAcross * circlesDown;
circles = new Array(circlesToAdd);
while (circlesToAdd) {
const i = circles.length - circlesToAdd;
const column = ((i + 1) + circlesAcross) % circlesAcross || circlesAcross;
const row = Math.floor(i / circlesAcross) + 1;
circles[i] = {
x: ((vw - (stepDistanceX * (circlesAcross - 1))) / 2) + (stepDistanceX * (column - 1)),
y: ((vh - (stepDistanceY * (circlesDown - 1))) / 2) + (stepDistanceY * (row - 1)),
drawn: false
};
circlesToAdd--;
}
}
// Gets the closest circle.
function getClosestCircle(x, y) {
return circles[circles.map((circle, i) => {
return {dist: Math.abs(circle.x - x) + Math.abs(circle.y - y), index: i };
}).sort((a, b) => {
return a.dist - b.dist;
})[0].index]
}
// Gets the closest circles by range.
function getClosestCircles(x, y, range) {
return circles.filter(circle => {
return Math.abs(circle.x - x) + Math.abs(circle.y - y) < range;
})
}
// Handle click event.
function getPosition(event){
if (event.srcElement.tagName === "A" || isZooming) {
return true;
}
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left; // x == the location of the click in the document - the location (relative to the left) of the canvas in the document
const y = event.clientY - rect.top; // y == the location of the click in the document - the location (relative to the top) of the canvas in the document
if (clickNum < 1) {
// First click.
originCircle = getClosestCircle(x,y);
drawStuff([originCircle], x, y);
}
else {
// Add from origin.
drawStuff(getClosestCircles(originCircle.x, originCircle.y, Math.max(clickNum * stepDistanceX, clickNum * stepDistanceY)), x, y);
}
clickNum++;
}
// This is the zoom animation.
function zoomReset() {
// break loop if no canvas.
if (!canvas) {
return true;
}
frame = requestAnimationFrame(zoomReset);
// Loop it.
if (dt < 1 && isZooming) {
dt += 0.08; //determines speed
// Do alot of stuff in the scratch pad.
ctxS.clearRect(0, 0, scratch.width, scratch.height);
const tx = easing(dt) * dx - (((scratch.width - canvas.width) / 2) * (1 - dt));
const ty = easing(dt) * dy - (((scratch.height - canvas.height) / 2) * (1 - dt));
const ts = 1 - ds * (easing(dt) * 1);
// set elements by tx
ctxS.putImageData(zoomingImage, (scratch.width - canvas.width) / 2, (scratch.height - canvas.height) / 2);
ctxS.beginPath();
ctxS.arc(scratch.width / 2, scratch.height / 2, Math.max(scratch.width / 2, scratch.height / 2), 0, Math.PI * 2);
ctxS.clip();
ctxS.fillStyle = `rgba(255, 79, 23, ${(1 * dt) - (0.2 / (1 * (dt * 2)))})`;
ctxS.fillRect(0, 0, scratch.width, scratch.height);
// Update on main canvas.
ctx.clearRect(0, 0, vw, vh);
ctx.drawImage(scratch, Math.floor(tx), Math.floor(ty), Math.floor(scratch.width * ts), Math.floor(scratch.height * ts));
}
else if (isZooming) {
isZooming = false;
drawStuff([getClosestCircle(...destination)]);
}
}
// Draw stuff on the canvas.
function drawStuff(stuffToDraw = [], x, y) {
// Do circles.
ctx.clearRect(0, 0, vw, vh);
stuffToDraw.forEach(circle => {
ctx.fillStyle = "#FF4F17";
ctx.beginPath(); //Start path
ctx.arc(circle.x, circle.y, circleRadius, 0, Math.PI * 2, true); // Draw a point using the arc function of the canvas with a point structure.
ctx.fill(); // Close the path and fill.
circle.drawn = true;
});
// Do our zoom.
if (!circles.filter(circle => !circle.drawn).length && isZooming === false) {
originCircle = getClosestCircle(x,y);
const {x:nx, y:ny} = originCircle;
destination = [nx,ny];
ds = Math.min(1 - (circleRadius / vw), 1 - (circleRadius / vh));
dx = nx - ((scratch.width * (1 - ds)) / 2);
dy = ny - ((scratch.height * (1 - ds)) / 2);
zoomingImage = zoomingImage ? zoomingImage : ctx.getImageData(0, 0, canvas.width, canvas.height);
clickNum = 1;
dt = 0;
circles.forEach(circle => {
circle.drawn = false;
});
isZooming = true;
}
}
// Start.
canvas.addEventListener("click", getPosition);
resize();
setCircleSizes();
setCircleMatrix();
frame = requestAnimationFrame(zoomReset);
<canvas class="canvas"></canvas>
UPDATE: I've found that if I reset the scratch element after using the loop scratch = document.createElement('canvas'); resize(); ctxS = scratch.getContext('2d', { alpha: false });, the animation works as fast each time like the first time. Any ideas as to why that is the case?

How can we stop this HTML5 Canvas wheel at exact points after spin?

In the Below code link HTML5 canvas spin wheel game. I want to stop this canvas at a user-defined position as if the user wants to stop always at 200 texts or 100 texts like that.
Currently, it is stopping at random points I want to control where to stop as in if I want to stop circle at 100 or 200 or 0 whenever I want.
How can we achieve that??? Can anyone Help!!!!!
Attached Codepen link also.
Html file
<div>
<canvas class="spin-wheel" id="canvas" width="300" height="300"></canvas>
</div>
JS file
var color = ['#ca7','#7ac','#77c','#aac','#a7c','#ac7', "#caa"];
var label = ['10', '200','50','100','5','500',"0"];
var slices = color.length;
var sliceDeg = 360/slices;
var deg = 270;
var speed = 5;
var slowDownRand = 0;
var ctx = canvas.getContext('2d');
var width = canvas.width; // size
var center = width/2; // center
var isStopped = false;
var lock = false;
function rand(min, max) {
return Math.random() * (max - min) + min;
}
function deg2rad(deg){ return deg * Math.PI/180; }
function drawSlice(deg, color){
ctx.beginPath();
ctx.fillStyle = color;
ctx.moveTo(center, center);
ctx.arc(center, center, width/2, deg2rad(deg), deg2rad(deg+sliceDeg));
console.log(center, center, width/2, deg2rad(deg), deg2rad(deg+sliceDeg))
ctx.lineTo(center, center);
ctx.fill();
}
function drawText(deg, text) {
ctx.save();
ctx.translate(center, center);
ctx.rotate(deg2rad(deg));
ctx.textAlign = "right";
ctx.fillStyle = "#fff";
ctx.font = 'bold 30px sans-serif';
ctx.fillText(text, 130, 10);
ctx.restore();
}
function drawImg() {
ctx.clearRect(0, 0, width, width);
for(var i=0; i<slices; i++){
drawSlice(deg, color[i]);
drawText(deg+sliceDeg/2, label[i]);
deg += sliceDeg;
}
}
// ctx.rotate(360);
function anim() {
isStopped = true;
deg += speed;
deg %= 360;
// Increment speed
if(!isStopped && speed<3){
speed = speed+1 * 0.1;
}
// Decrement Speed
if(isStopped){
if(!lock){
lock = true;
slowDownRand = rand(0.994, 0.998);
}
speed = speed>0.2 ? speed*=slowDownRand : 0;
}
// Stopped!
if(lock && !speed){
var ai = Math.floor(((360 - deg - 90) % 360) / sliceDeg); // deg 2 Array Index
console.log(slices)
ai = (slices+ai)%slices; // Fix negative index
return alert("You got:\n"+ label[ai] ); // Get Array Item from end Degree
// ctx.arc(150,150,150,8.302780584487312,9.200378485512967);
// ctx.fill();
}
drawImg();
window.requestAnimationFrame(anim);
}
function start() {
anim()
}
drawImg();
Spin wheel codepen
Ease curves
If you where to plot the wheel position over time as it slows to a stop you would see a curve, a curve that looks like half a parabola.
You can get the very same curve if you plot the value of x squared in the range 0 to 1 as in the next snippet, the red line shows the plot of f(x) => x * x where 0 <= x <= 1
Unfortunately the plot is the wrong way round and needs to be mirrored in x and y. That is simple by changing the function to f(x) => 1 - (1 - x) ** 2 (Click the canvas to get the yellow line)
const size = 200;
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size / 2}).getContext("2d");
document.body.appendChild(ctx.canvas);
ctx.canvas.style.border = "2px solid black";
plot(getData());
plot(unitCurve(x => x * x), "#F00");
ctx.canvas.addEventListener("click",()=>plot(unitCurve(x => 1 - (1 - x) ** 2), "#FF0"), {once: true});
function getData(chart = []) {
var pos = 0, speed = 9, deceleration = 0.1;
while(speed > 0) {
chart.push(pos);
pos += speed;
speed -= deceleration;
}
return chart;
}
function unitCurve(f,chart = []) {
const step = 1 / 100;
var x = 0;
while(x <= 1) {
chart.push(f(x));
x += step
}
return chart;
}
function plot(chart, col = "#000") {
const xScale = size / chart.length, yScale = size / 2 / Math.max(...chart);
ctx.setTransform(xScale, 0, 0, yScale, 0, 0);
ctx.strokeStyle = col;
ctx.beginPath();
chart.forEach((y,x) => ctx.lineTo(x,y));
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
In animation this curve is an ease in.
We can create function that uses the ease function, takes the time and returns the position of the wheel. We can provide some additional values that controls how long the wheel will take to stop, the starting position and the all important stop position.
function wheelPos(currentTime, startTime, endTime, startPos, endPos) {
// first scale the current time to a value from 0 to 1
const x = (currentTime - startTime) / (endTime - startTime);
// rather than the square, we will use the square root (this flips the curve)
const xx = x ** (1 / 2);
// convert the value to a wheel position
return xx * (endPos - startPos) + startPos;
}
Demo
The demo puts it in action. Rather than using the square root the function in the demo defines the root as the constant slowDownRate = 2.6. The smaller this value the greater start speed and the slower the end speed. A value of 1 means it will move at a constant speed and then stop. The value must be > 0 and < 1
requestAnimationFrame(mainLoop);
Math.TAU = Math.PI * 2;
const size = 160;
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size}).getContext("2d");
document.body.appendChild(ctx.canvas);
const stopAt = document.createElement("div")
document.body.appendChild(stopAt);
ctx.canvas.style.border = "2px solid black";
var gTime; // global time
const colors = ["#F00","#F80","#FF0","#0C0","#08F","#00F","#F0F"];
const wheelSteps = 12;
const minSpins = 3 * Math.TAU; // min number of spins before stopping
const spinTime = 6000; // in ms
const slowDownRate = 1 / 1.8; // smaller this value the greater the ease in.
// Must be > 0
var startSpin = false;
var readyTime = 0;
ctx.canvas.addEventListener("click",() => { startSpin = !wheel.spinning });
stopAt.textContent = "Click wheel to spin";
const wheel = { // hold wheel related variables
img: createWheel(wheelSteps),
endTime: performance.now() - 2000,
startPos: 0,
endPos: 0,
speed: 0,
pos: 0,
spinning: false,
set currentPos(val) {
this.speed = (val - this.pos) / 2; // for the wobble at stop
this.pos = val;
},
set endAt(pos) {
this.endPos = (Math.TAU - (pos / wheelSteps) * Math.TAU) + minSpins;
this.endTime = gTime + spinTime;
this.startTime = gTime;
stopAt.textContent = "Spin to: "+(pos + 1);
}
};
function wheelPos(currentTime, startTime, endTime, startPos, endPos) {
const x = ((currentTime - startTime) / (endTime - startTime)) ** slowDownRate;
return x * (endPos - startPos) + startPos;
}
function mainLoop(time) {
gTime = time;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, size, size);
if (startSpin && !wheel.spinning) {
startSpin = false;
wheel.spinning = true;
wheel.startPos = (wheel.pos % Math.TAU + Math.TAU) % Math.TAU;
wheel.endAt = Math.random() * wheelSteps | 0;
} else if (gTime <= wheel.endTime) { // wheel is spinning get pos
wheel.currentPos = wheelPos(gTime, wheel.startTime, wheel.endTime, wheel.startPos, wheel.endPos);
readyTime = gTime + 1500;
} else { // wobble at stop
wheel.speed += (wheel.endPos - wheel.pos) * 0.0125;
wheel.speed *= 0.95;
wheel.pos += wheel.speed;
if (wheel.spinning && gTime > readyTime) {
wheel.spinning = false;
stopAt.textContent = "Click wheel to spin";
}
}
// draw wheel
ctx.setTransform(1,0,0,1,size / 2, size / 2);
ctx.rotate(wheel.pos);
ctx.drawImage(wheel.img, -size / 2 , - size / 2);
// draw marker shadow
ctx.setTransform(1,0,0,1,1,4);
ctx.fillStyle = "#0004";
ctx.beginPath();
ctx.lineTo(size - 13, size / 2);
ctx.lineTo(size, size / 2 - 7);
ctx.lineTo(size, size / 2 + 7);
ctx.fill();
// draw marker
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.lineTo(size - 13, size / 2);
ctx.lineTo(size, size / 2 - 7);
ctx.lineTo(size, size / 2 + 7);
ctx.fill();
requestAnimationFrame(mainLoop);
}
function createWheel(steps) {
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size}).getContext("2d");
const s = size, s2 = s / 2, r = s2 - 4;
var colIdx = 0;
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const aa = a - Math.PI / steps;
ctx.fillStyle = colors[colIdx++ % colors.length];
ctx.beginPath();
ctx.moveTo(s2, s2);
ctx.arc(s2, s2, r, aa, aa + Math.TAU / steps);
ctx.fill();
}
ctx.fillStyle = "#FFF";
ctx.beginPath();
ctx.arc(s2, s2, 12, 0, Math.TAU);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.arc(s2, s2, r, 0, Math.TAU);
ctx.moveTo(s2 + 12, s2);
ctx.arc(s2, s2, 12, 0, Math.TAU);
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const aa = a - Math.PI / steps;
ctx.moveTo(Math.cos(aa) * 12 + s2, Math.sin(aa) * 12 + s2);
ctx.lineTo(Math.cos(aa) * r + s2, Math.sin(aa) * r + s2);
}
//ctx.fill("evenodd");
ctx.stroke();
ctx.fillStyle = "#000";
ctx.font = "13px arial black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const tr = r - 8;
var idx = 1;
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const dx = Math.cos(a);
const dy = Math.sin(a);
ctx.setTransform(dy, -dx, dx, dy, dx * (tr - 4) + s2, dy * (tr - 4) + s2);
ctx.fillText(""+ (idx ++), 0, 0);
}
return ctx.canvas;
}
body { font-family: arial }

Canvas problems. Not able to reproduce design

I need to build canvas animation like design requires. I spend almost 3 days but I'm not able to do anything like in design. Here a REQUESTED design!. And here - what I've got for now: current implementation which definitely not what requested from design .I need only animation of planet from particles at background (also whole process of animation changes in time, it starts from few particles but then amount growing and movings directions of particles changes)
here my current code:
export class CanvasComponent implements OnInit {
sphereRad = 280;
radius_sp = 1;
distance = 600;
particle_size = 0.7;
constructor() { }
ngOnInit() {
this.canvasApp();
}
canvasApp () {
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let displayWidth;
let displayHeight;
let wait;
let count;
let numToAddEachFrame;
let particleList;
let recycleBin;
let particleAlpha;
let r, g, b;
let fLen;
let m;
let projCenterX;
let projCenterY;
let zMax;
let turnAngle;
let turnSpeed;
let sphereCenterX, sphereCenterY, sphereCenterZ;
let particleRad;
let zeroAlphaDepth;
let randAccelX, randAccelY, randAccelZ;
let gravity;
let rgbString;
// we are defining a lot of letiables used in the screen update functions globally so that they don't have to be redefined every frame.
let p;
let outsideTest;
let nextParticle;
let sinAngle;
let cosAngle;
let rotX, rotZ;
let depthAlphaFactor;
let i;
let theta, phi;
let x0, y0, z0;
// INITIALLI
const init = () => {
wait = 1;
count = wait - 1;
numToAddEachFrame = 30;
// particle color
r = 255;
g = 255;
b = 255;
rgbString = 'rgba(' + r + ',' + g + ',' + b + ','; // partial string for color which will be completed by appending alpha value.
particleAlpha = 1; // maximum alpha
displayWidth = canvas.width;
displayHeight = canvas.height;
fLen = this.distance; // represents the distance from the viewer to z=0 depth.
// projection center coordinates sets location of origin
projCenterX = displayWidth / 2;
projCenterY = displayHeight / 2;
// we will not draw coordinates if they have too large of a z-coordinate (which means they are very close to the observer).
zMax = fLen - 2;
particleList = {};
recycleBin = {};
// random acceleration factors - causes some random motion
randAccelX = 0.1;
randAccelY = 0.1;
randAccelZ = 0.1;
gravity = -0; // try changing to a positive number (not too large, for example 0.3), or negative for floating upwards.
particleRad = this.particle_size;
sphereCenterX = 0;
sphereCenterY = 0;
sphereCenterZ = -3 - this.sphereRad;
// alpha values will lessen as particles move further back, causing depth-based darkening:
zeroAlphaDepth = 0;
turnSpeed = 2 * Math.PI / 1200; // the sphere will rotate at this speed (one complete rotation every 1600 frames).
turnAngle = 0; // initial angle
// timer = setInterval(onTimer, 10 / 24);
onTimer();
}
const onTimer = () => {
// if enough time has elapsed, we will add new particles.
count++;
if (count >= wait) {
count = 0;
for (i = 0; i < numToAddEachFrame; i++) {
theta = Math.random() * 2 * Math.PI;
phi = Math.acos(Math.random() * 2 - 1);
x0 = this.sphereRad * Math.sin(phi) * Math.cos(theta);
y0 = this.sphereRad * Math.sin(phi) * Math.sin(theta);
z0 = this.sphereRad * Math.cos(phi);
// We use the addParticle function to add a new particle. The parameters set the position and velocity components.
// Note that the velocity parameters will cause the particle to initially fly outwards away from the sphere center (after
// it becomes unstuck).
const p = addParticle(x0, sphereCenterY + y0, sphereCenterZ + z0, 0.002 * x0, 0.002 * y0, 0.002 * z0);
// we set some 'envelope' parameters which will control the evolving alpha of the particles.
p.attack = 50;
p.hold = 50;
p.decay = 100;
p.initValue = 0;
p.holdValue = particleAlpha;
p.lastValue = 0;
// the particle will be stuck in one place until this time has elapsed:
p.stuckTime = 90 + Math.random() * 20;
p.accelX = 0;
p.accelY = gravity;
p.accelZ = 0;
}
}
// update viewing angle
turnAngle = (turnAngle + turnSpeed) % (2 * Math.PI);
sinAngle = Math.sin(turnAngle);
cosAngle = Math.cos(turnAngle);
// background fill
context.fillStyle = '#000000';
context.fillRect(0, 0, displayWidth, displayHeight);
// update and draw particles
p = particleList.first;
while (p != null) {
// before list is altered record next particle
nextParticle = p.next;
// update age
p.age++;
// if the particle is past its 'stuck' time, it will begin to move.
if (p.age > p.stuckTime) {
p.velX += p.accelX + randAccelX * (Math.random() * 2 - 1);
p.velY += p.accelY + randAccelY * (Math.random() * 2 - 1);
p.velZ += p.accelZ + randAccelZ * (Math.random() * 2 - 1);
p.x += p.velX;
p.y += p.velY;
p.z += p.velZ;
}
/*
We are doing two things here to calculate display coordinates.
The whole display is being rotated around a vertical axis, so we first calculate rotated coordinates for
x and z (but the y coordinate will not change).
Then, we take the new coordinates (rotX, y, rotZ), and project these onto the 2D view plane.
*/
rotX = cosAngle * p.x + sinAngle * (p.z - sphereCenterZ);
rotZ = -sinAngle * p.x + cosAngle * (p.z - sphereCenterZ) + sphereCenterZ;
// m = this.radius_sp * fLen / (fLen - rotZ);
m = this.radius_sp;
p.projX = rotX * m + projCenterX;
p.projY = p.y * m + projCenterY;
p.projZ = rotZ * m + projCenterX;
// update alpha according to envelope parameters.
if (p.age < p.attack + p.hold + p.decay) {
if (p.age < p.attack) {
p.alpha = (p.holdValue - p.initValue) / p.attack * p.age + p.initValue;
} else if (p.age < p.attack + p.hold) {
p.alpha = p.holdValue;
} else if (p.age < p.attack + p.hold + p.decay) {
p.alpha = (p.lastValue - p.holdValue) / p.decay * (p.age - p.attack - p.hold) + p.holdValue;
}
} else {
p.dead = true;
}
// see if the particle is still within the viewable range.
if ((p.projX > displayWidth) || (p.projX < 0) || (p.projY < 0) || (p.projY > displayHeight) || (rotZ > zMax)) {
outsideTest = true;
} else {
outsideTest = false;
}
if (outsideTest || p.dead ||
(p.projX > displayWidth / (2 + (1 - Math.random())) && p.projZ + displayWidth * 0.1 > displayWidth / 2) ||
(p.projX < displayWidth / (2 - (1 - Math.random())) && p.projZ + displayWidth * 0.25 < displayWidth / 2)
) {
recycle(p);
} else {
// depth-dependent darkening
// console.log(turnAngle, rotZ)
depthAlphaFactor = 1;
// depthAlphaFactor = (1 - (1.5 + rotZ / 100));
depthAlphaFactor = (depthAlphaFactor > 1) ? 1 : ((depthAlphaFactor < 0) ? 0 : depthAlphaFactor);
context.fillStyle = rgbString + depthAlphaFactor * p.alpha + ')';
// draw
context.beginPath();
context.arc(p.projX, p.projY, m * particleRad, 0, 2 * Math.PI, false);
context.closePath();
context.fill();
}
p = nextParticle;
}
window.requestAnimationFrame(onTimer);
}
const addParticle = (x0, y0, z0, vx0, vy0, vz0) => {
let newParticle;
// const color;
// check recycle bin for available drop:
if (recycleBin.first != null) {
newParticle = recycleBin.first;
// remove from bin
if (newParticle.next != null) {
recycleBin.first = newParticle.next;
newParticle.next.prev = null;
} else {
recycleBin.first = null;
}
} else {
newParticle = {};
}
// if the recycle bin is empty, create a new particle (a new empty object):
// add to beginning of particle list
if (particleList.first == null) {
particleList.first = newParticle;
newParticle.prev = null;
newParticle.next = null;
} else {
newParticle.next = particleList.first;
particleList.first.prev = newParticle;
particleList.first = newParticle;
newParticle.prev = null;
}
// initialize
newParticle.x = x0;
newParticle.y = y0;
newParticle.z = z0;
newParticle.velX = vx0;
newParticle.velY = vy0;
newParticle.velZ = vz0;
newParticle.age = 0;
newParticle.dead = false;
if (Math.random() < 0.5) {
newParticle.right = true;
} else {
newParticle.right = false;
}
return newParticle;
}
const recycle = (p) => {
// remove from particleList
if (particleList.first === p) {
if (p.next != null) {
p.next.prev = null;
particleList.first = p.next;
} else {
particleList.first = null;
}
} else {
if (p.next == null) {
p.prev.next = null;
} else {
p.prev.next = p.next;
p.next.prev = p.prev;
}
}
// add to recycle bin
if (recycleBin.first == null) {
recycleBin.first = p;
p.prev = null;
p.next = null;
} else {
p.next = recycleBin.first;
recycleBin.first.prev = p;
recycleBin.first = p;
p.prev = null;
}
};
init();
}
}
So I will be happy with any help also REWARD(for full implementation) is possible (ETH, BTC any currency you wish).

How to make blur effect particles in javascript

Hi I want to make a blur effect particle like this:
Can I use shadowBlur and shadowOffsetX/shadowOffsetY to do this? The actual shine will glow and fade a little bit repeatedly, so if I have to write some kind of animation how can I achieve this?
I have tried this code (jsfiddle example) but it doesn't look like the effect. So I wonder how to blur and glow the particle at the same time?
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ra = window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.oRequestAnimationFrame
|| window.msRequestAnimationFrame
|| function(callback) {
window.setTimeout(callback, 1000 / 60);
};
class Particle {
constructor(options) {
this.ctx = options.context;
this.x = options.x;
this.y = options.y;
this.radius = options.radius;
this.lightSize = this.radius;
this.color = options.color;
this.lightDirection = true;
}
glow() {
const lightSpeed = 0.5;
this.lightSize += this.lightDirection ? lightSpeed : -lightSpeed;
if (this.lightSize > this.radius || this.lightSize < this.radius) {
this.lightDirection = !this.lightDirection;
}
}
render() {
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.glow();
this.ctx.globalAlpha = 0.5;
this.ctx.fillStyle = this.color;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.lightSize,
0, Math.PI * 2
);
this.ctx.fill();
this.ctx.globalAlpha = 0.62;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius * 0.7, 0, Math.PI * 2);
this.ctx.shadowColor = this.color;
this.ctx.shadowBlur = 6;
this.ctx.shadowOffsetX = 0;
this.ctx.shadowOffsetY = 0;
this.ctx.fill();
}
}
var particle = new Particle({
context: ctx,
x: 60,
y: 80,
radius: 12,
color: '#4d88ff'
});
function run() {
particle.render();
ra(run);
}
run();
<canvas id='canvas'></canvas>
There are several ways to do this. For a particle system my option is to pre render the blur using a blur filter. A common filter is the convolution filter. It uses a small array to determine the amount neighboring pixels contribute to each pixel of the image. You are best to look up convolution functions to understand it.
Wiki Convolution and Wiki Gaussian blur for more info.
I am not much of a fan of the standard Gaussian blur or the convolution filter used so in the demo snippet below you can find my version that I think creates a much better blur. The convolution blur filter is procedurally created and is in the imageTools object.
To use create a filter pass an object with properties size the blur amount in pixels and power is the strength. Lower powers is less spread on the blur.
// image must be loaded or created
var blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
// apply the convolution filter on the image. The returned image may be a new
//image if the input image does not have a ctx property pointing to a 2d canvas context
image = imageTools.applyConvolutionFilter(image,blurFilter);
In the demo I create a image, draw a circle on it, copy it and pad it so that there is room for the blur. Then create a blur filter and apply it to the image.
When I render the particles I first draw all the unblurred images, then draw the blurred copies with the ctx.globalCompositeOperation = "screen"; so that they have a shine. To vary the amount of shine I use the ctx.globalAlpha to vary the intensity of the rendered blurred image. To improve the FX I have drawn the blur image twice, once with oscillating scale and next at fixed scale and alpha.
The demo is simple, image tools can be found at the top. Then there is some stuff to setup the canvas and handle resize event. Then there is the code that creates the images, and apply the filters. Then starts the render adds some particles and renders everything.
Look in the function drawParticles for how I draw everything.
imageTools has all the image functions you will need. The imageTools.applyConvolutionFilter will apply any filter (sharpen, outline, and many more) you just need to create the appropriate filter. The apply uses the photon count colour model so gives a very high quality result especially for blurs type effects. (though for sharpen you may want to get in and change the squaring of the RGB values, I personally like it other do not)
The blur filter is not fast so if you apply it to larger images It would be best that you break it up in so you do not block the page execution.
A cheap way to get a blur is to copy the image to blur to a smaller version of itself, eg 1/4 then render it scaled back to normal size, the canvas will apply bilinear filtering on the image give a blur effect. Not the best quality but for most situations it is indistinguishable from the more sophisticated blur that I have presented.
UPDATE
Change the code so that the particles have a bit of a 3dFX to show that the blur can work up to larger scales. The blue particles are 32 by 32 image and the blur is 9 pixels with the blur image being 50by 50 pixels.
var imageTools = (function () {
var tools = {
canvas : function (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 image = this.canvas(width, height);
image.ctx = image.getContext("2d");
return image;
},
image2Canvas : function (img) {
var image = this.canvas(img.width, img.height);
image.ctx = image.getContext("2d");
image.drawImage(img, 0, 0);
return image;
},
padImage : function(img,amount){
var image = this.canvas(img.width + amount * 2, img.height + amount * 2);
image.ctx = image.getContext("2d");
image.ctx.drawImage(img, amount, amount);
return image;
},
getImageData : function (image) {
return (image.ctx || (this.image2Canvas(image).ctx)).getImageData(0, 0, image.width, image.height);
},
putImageData : function (image, imgData){
(image.ctx || (this.image2Canvas(image).ctx)).putImageData(imgData,0, 0);
return image;
},
createBlurConvolutionArray : function(options){
var i, j, d; // misc vars
var filterArray = []; // the array to create
var size = options.size === undefined ? 3: options.size; // array size
var center = Math.floor(size / 2); // center of array
// the power ? needs descriptive UI options
var power = options.power === undefined ? 1: options.power;
// dist to corner
var maxDist = Math.sqrt(center * center + center * center);
var dist = 0; // distance sum
var sum = 0; // weight sum
var centerWeight; // center calculated weight
var totalDistance; // calculated total distance from center
// first pass get the total distance
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
dist += d;
}
}
totalDistance = dist; // total distance to all points;
// second pass get the total weight of all but center
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
d = d/totalDistance;
sum += d;
}
}
var scale = 1/sum;
sum = 0; // used to check
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
d = d/totalDistance;
filterArray.push(d*scale);
}
}
return filterArray;
},
applyConvolutionFilter : function(image,filter){
imageData = this.getImageData(image);
imageDataResult = this.getImageData(image);
var w = imageData.width;
var h = imageData.height;
var data = imageData.data;
var data1 = imageDataResult.data;
var side = Math.round(Math.sqrt(filter.length));
var halfSide = Math.floor(side/2);
var r,g,b,a,c;
for(var y = 0; y < h; y++){
for(var x = 0; x < w; x++){
var ind = y*4*w+x*4;
r = 0;
g = 0;
b = 0;
a = 0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = y + cy - halfSide;
var scx = x + cx - halfSide;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
var srcOff = (scy*w+scx)*4;
var wt = filter[cy*side+cx];
r += data[srcOff+0] * data[srcOff+0] * wt;
g += data[srcOff+1] * data[srcOff+1] * wt;
b += data[srcOff+2] * data[srcOff+2] * wt;
a += data[srcOff+3] * data[srcOff+3] * wt;
}
}
}
data1[ind+0] = Math.sqrt(Math.max(0,r));
data1[ind+1] = Math.sqrt(Math.max(0,g));
data1[ind+2] = Math.sqrt(Math.max(0,b));
data1[ind+3] = Math.sqrt(Math.max(0,a));
}
}
return this.putImageData(image,imageDataResult);
}
};
return tools;
})();
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){
cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2;
if(particles && particles.length > 0){
particles.length = 0;
}
}
resizeCanvas(); // create and size canvas
window.addEventListener("resize",resizeCanvas); // add resize event
const IMAGE_SIZE = 32;
const IMAGE_SIZE_HALF = 16;
const GRAV = 2001;
const NUM_PARTICLES = 90;
var background = imageTools.createImage(8,8);
var grad = ctx.createLinearGradient(0,0,0,8);
grad.addColorStop(0,"#000");
grad.addColorStop(1,"#048");
background.ctx.fillStyle = grad;
background.ctx.fillRect(0,0,8,8);
var circle = imageTools.createImage(IMAGE_SIZE,IMAGE_SIZE);
circle.ctx.fillStyle = "#5BF";
circle.ctx.arc(IMAGE_SIZE_HALF, IMAGE_SIZE_HALF, IMAGE_SIZE_HALF -2,0, Math.PI * 2);
circle.ctx.fill();
var blurFilter = imageTools.createBlurConvolutionArray({size:9,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
var blurCircle = imageTools.padImage(circle,9);
blurCircle = imageTools.applyConvolutionFilter(blurCircle,blurFilter)
var sun = imageTools.createImage(64,64);
grad = ctx.createRadialGradient(32,32,0,32,32,32);
grad.addColorStop(0,"#FF0");
grad.addColorStop(1,"#A40");
sun.ctx.fillStyle = grad;
sun.ctx.arc(32,32,32 -2,0, Math.PI * 2);
sun.ctx.fill();
var sunBlur = imageTools.padImage(sun,17);
blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
sunBlur = imageTools.applyConvolutionFilter(sunBlur,blurFilter);
var particles = [];
var createParticle = function(x,y,dx,dy){
var dir = Math.atan2(y-ch,x-cw);
var dist = Math.sqrt(Math.pow(y-ch,2)+Math.pow(x-cw,2));
var v = Math.sqrt(GRAV / dist); // get apporox orbital speed
return {
x : x,
y : y,
dx : dx + Math.cos(dir + Math.PI/2) * v, // set orbit speed at tangent
dy : dy + Math.sin(dir + Math.PI/2) * v,
s : (Math.random() + Math.random() + Math.random())/4 + 0.5, // scale
v : (Math.random() + Math.random() + Math.random()) / 3 + 2, // glow vary rate
};
}
var depthSort = function(a,b){
return b.y - a.y;
}
var updateParticles = function(){
var i,p,f,dist,dir;
for(i = 0; i < particles.length; i ++){
p = particles[i];
dist = Math.sqrt(Math.pow(cw-p.x,2)+Math.pow(ch-p.y,2));
dir = Math.atan2(ch-p.y,cw-p.x);
f = GRAV * 1 / (dist * dist);
p.dx += Math.cos(dir) * f;
p.dy += Math.sin(dir) * f;
p.x += p.dx;
p.y += p.dy;
p.rx = ((p.x - cw ) / (p.y + h)) * h + cw;
p.ry = ((p.y - ch ) / (p.y + h)) * h * -0.051+ ch;
//p.ry = ((h-p.y) - ch) * 0.1 + ch;
p.rs = (p.s / (p.y + h)) * h
}
particles.sort(depthSort)
}
var drawParticles = function(){
var i,j,p,f,dist,dir;
// draw behind the sun
for(i = 0; i < particles.length; i ++){
p = particles[i];
if(p.y - ch < 0){
break;
}
ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
}
// draw glow for behind the sun
ctx.globalCompositeOperation = "screen";
var iw = -blurCircle.width/2;
for(j = 0; j < i; j ++){
p = particles[j];
ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.4;
var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.6;
ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
// second pass to intensify the glow
ctx.globalAlpha = 0.7;
ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
}
// draw the sun
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,cw,ch);
ctx.drawImage(sun,-sun.width/2,-sun.height/2);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "screen";
ctx.setTransform(1,0,0,1,cw,ch);
ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
var scale = Math.sin(globalTime / 100) *0.5 + 1;
ctx.globalAlpha = (Math.cos(globalTime / 100) + 1) * 0.2 + 0.4;;
ctx.setTransform(1 + scale,0,0,1 + scale,cw,ch);
ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
// draw in front the sun
for(j = i; j < particles.length; j ++){
p = particles[j];
if(p.y > -h){ // don't draw past the near view plane
ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
}
}
ctx.globalCompositeOperation = "screen";
var iw = -blurCircle.width/2;
for(j = i; j < particles.length; j ++){
p = particles[j];
if(p.y > -h){ // don't draw past the near view plane
ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.4;
var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.6;
ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
// second pass to intensify the glow
ctx.globalAlpha = 0.7;
ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
}
}
ctx.globalCompositeOperation = "source-over";
}
var addParticles = function(count){
var ww = (h-10)* 2;
var cx = cw - ww/2;
var cy = ch - ww/2;
for(var i = 0; i < count; i ++){
particles.push(createParticle(cx + Math.random() * ww,cy + Math.random() * ww, Math.random() - 0.5, Math.random() - 0.5));
}
}
function display(){ // put code in here
if(particles.length === 0){
addParticles(NUM_PARTICLES);
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.drawImage(background,0,0,w,h)
updateParticles();
drawParticles();
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/

multiple random images in let it snow plug-in

i've been playing with jason brown's let it snow plug-in.
his code only accommodates for a single custom image, and i've been trying to figure out how to change the code so it accommodates multiple custom images within a random range.
!function($){
var defaults = {
speed: 0,
interaction: true,
size: 2,
count: 200,
opacity: 0,
color: "#ffffff",
windPower: 0,
image: false
};
$.fn.let_it_snow = function(options){
var settings = $.extend({}, defaults, options),
el = $(this),
flakes = [],
canvas = el.get(0),
ctx = canvas.getContext("2d"),
flakeCount = settings.count,
mX = -100,
mY = -100;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
function snow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = flakes[i],
x = mX,
y = mY,
minDist = 100,
x2 = flake.x,
y2 = flake.y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
dx = x2 - x,
dy = y2 - y;
if (dist < minDist) {
var force = minDist / (dist * dist),
xcomp = (x - x2) / dist,
ycomp = (y - y2) / dist,
deltaV = force / 2;
flake.velX -= deltaV * xcomp;
flake.velY -= deltaV * ycomp;
} else {
flake.velX *= .98;
if (flake.velY <= flake.speed) {
flake.velY = flake.speed
}
switch (settings.windPower) {
case false:
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
break;
case 0:
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
break;
default:
flake.velX += 0.01 + (settings.windPower/100);
}
}
var s = settings.color;
var patt = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/;
var matches = patt.exec(s);
var rgb = parseInt(matches[1], 16)+","+parseInt(matches[2], 16)+","+parseInt(matches[3], 16);
flake.y += flake.velY;
flake.x += flake.velX;
if (flake.y >= canvas.height || flake.y <= 0) {
reset(flake);
}
if (flake.x >= canvas.width || flake.x <= 0) {
reset(flake);
}
if (settings.image == false) {
ctx.fillStyle = "rgba(" + rgb + "," + flake.opacity + ")"
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
} else {
ctx.drawImage($("img#lis_flake").get(0), flake.x, flake.y, flake.size * 2, flake.size * 2);
}
}
requestAnimationFrame(snow);
};
function reset(flake) {
if (settings.windPower == false || settings.windPower == 0) {
flake.x = Math.floor(Math.random() * canvas.width);
flake.y = 0;
} else {
if (settings.windPower > 0) {
var xarray = Array(Math.floor(Math.random() * canvas.width), 0);
var yarray = Array(0, Math.floor(Math.random() * canvas.height))
var allarray = Array(xarray, yarray)
var selected_array = allarray[Math.floor(Math.random()*allarray.length)];
flake.x = selected_array[0];
flake.y = selected_array[1];
} else {
var xarray = Array(Math.floor(Math.random() * canvas.width),0);
var yarray = Array(canvas.width, Math.floor(Math.random() * canvas.height))
var allarray = Array(xarray, yarray)
var selected_array = allarray[Math.floor(Math.random()*allarray.length)];
flake.x = selected_array[0];
flake.y = selected_array[1];
}
}
flake.size = (Math.random() * 3) + settings.size;
flake.speed = (Math.random() * 1) + settings.speed;
flake.velY = flake.speed;
flake.velX = 0;
flake.opacity = (Math.random() * 0.5) + settings.opacity;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = Math.floor(Math.random() * canvas.width),
y = Math.floor(Math.random() * canvas.height),
size = (Math.random() * 3) + settings.size,
speed = (Math.random() * 1) + settings.speed,
opacity = (Math.random() * 0.5) + settings.opacity;
flakes.push({
speed: speed,
velY: speed,
velX: 0,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
angle: 180,
opacity: opacity
});
}
snow();
}
if (settings.image != false) {
$("<img src='"+settings.image+"' style='display: none' id='lis_flake'>").prependTo("body")
}
init();
$(window).resize(function() {
if(this.resizeTO) clearTimeout(this.resizeTO);
this.resizeTO = setTimeout(function() {
el2 = el.clone();
el2.insertAfter(el);
el.remove();
el2.let_it_snow(settings);
}, 200);
});
if (settings.interaction == true) {
canvas.addEventListener("mousemove", function(e) {
mX = e.clientX,
mY = e.clientY
});
}}}(window.jQuery);
right at the top of the code, in the defaults properties, is where you point the url of the image. below is what i've put in, so it chooses between image1.jpeg and image2.jpeg
image: "img/image'+Math.floor((Math.random() * 2) + 1)+'.jpeg";
however, when the "snowflakes" respawn, the image stays the same instead of choosing a random number again. what do i have to change so that when a snowflake is created, it chooses a random image and becomes it?
i hope my questions is clear, let me know if you need more clarity. i'm new to j-script, any help would be appreciated.
The plugin author loads a single custom image into a hidden element with
if (settings.image != false) {
$("<img src='"+settings.image+"' style='display: none' id='lis_flake'>").prependTo("body")
}
and loads the snowflakes with this
ctx.drawImage($("img#lis_flake").get(0)
This solution hasn't been tested, but if you are willing to hack the plugin a bit, you could potentially make the following changes to use two optional images:
1) Add this just before the init(); function call. It adds another hidden image from settings.image2.
if (settings.image2) {
$("<img src='"+settings.image2+"' style='display: none' id='lis_flake2'>").prependTo("body")
}
2) Modify the function init() with
flakes.push({
// Create a new property called 'imgNum' to hold either "" or "2";
imgNum: (settings.image2 && Math.floor(Math.random() * 2) === 0 ? "2" : ""),
// rest of the code ...
speed: speed,
3) Change
ctx.drawImage($("img#lis_flake").get(0), flake.x, flake.y, flake.size * 2, flake.size * 2);
to
ctx.drawImage($("img#lis_flake" + flake.imgNum).get(0), flake.x, flake.y, flake.size * 2, flake.size * 2);
to chose the image based on the flake.imgNum set randomly in init().
4) Change the options you pass into the plugin like so:
options = {
// other options
image: "img/image1.jpeg",
image2: "img/image2.jpeg",
// other options
}
These changes should allow the plugin to still work if image2 isn't set.

Categories

Resources