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.
Related
I am trying to replicate this effect - https://codepen.io/jonathasborges1/pen/YzryRpX in my angular app application.
But I'm having a hard time applying the effect
Someone can help me?
"use strict";
var LeafScene = function (el) {
this.viewport = el;
this.world = document.createElement("div");
this.leaves = [];
this.options = {
numLeaves: 60,
wind: {
magnitude: 1.2,
maxSpeed: 12,
duration: 300,
start: 0,
speed: 0
}
};
this.width = this.viewport.offsetWidth;
this.height = this.viewport.offsetHeight;
// animation helper
this.timer = 0;
this._resetLeaf = function (leaf) {
// place leaf towards the top left
leaf.x = this.width * 2 - Math.random() * this.width * 1.75;
leaf.y = -10;
leaf.z = Math.random() * 200;
if (leaf.x > this.width) {
leaf.x = this.width + 10;
leaf.y = (Math.random() * this.height) / 2;
}
// at the start, the leaf can be anywhere
if (this.timer == 0) {
leaf.y = Math.random() * this.height;
}
// Choose axis of rotation.
// If axis is not X, chose a random static x-rotation for greater variability
leaf.rotation.speed = Math.random() * 10;
var randomAxis = Math.random();
if (randomAxis > 0.5) {
leaf.rotation.axis = "X";
}
else if (randomAxis > 0.25) {
leaf.rotation.axis = "Y";
leaf.rotation.x = Math.random() * 180 + 90;
}
else {
leaf.rotation.axis = "Z";
leaf.rotation.x = Math.random() * 360 - 180;
// looks weird if the rotation is too fast around this axis
leaf.rotation.speed = Math.random() * 3;
}
// random speed
leaf.xSpeedVariation = Math.random() * 1 - 0.2;
leaf.ySpeed = Math.random();
return leaf;
};
this._updateLeaf = function (leaf) {
var leafWindSpeed = this.options.wind.speed(this.timer - this.options.wind.start, leaf.y);
var xSpeed = leafWindSpeed + leaf.xSpeedVariation;
leaf.x -= xSpeed;
leaf.y += leaf.ySpeed;
leaf.rotation.value += leaf.rotation.speed;
var t = "translateX( " +
leaf.x +
"px ) translateY( " +
leaf.y +
"px ) translateZ( " +
leaf.z +
"px ) rotate" +
leaf.rotation.axis +
"( " +
leaf.rotation.value +
"deg )";
if (leaf.rotation.axis !== "X") {
t += " rotateX(" + leaf.rotation.x + "deg)";
}
leaf.el.style.webkitTransform = t;
leaf.el.style.MozTransform = t;
leaf.el.style.oTransform = t;
leaf.el.style.transform = t;
// reset if out of view
if (leaf.x < -10 || leaf.y > this.height + 10) {
this._resetLeaf(leaf);
}
};
this._updateWind = function () {
if (this.timer === 0 ||
this.timer > this.options.wind.start + this.options.wind.duration) {
this.options.wind.magnitude = Math.random() * this.options.wind.maxSpeed;
this.options.wind.duration =
this.options.wind.magnitude * 50 + (Math.random() * 20 - 10);
this.options.wind.start = this.timer;
var screenHeight = this.height;
this.options.wind.speed = function (t, y) {
var a = ((this.magnitude / 2) * (screenHeight - (2 * y) / 3)) / screenHeight;
return (a *
Math.sin(((2 * Math.PI) / this.duration) * t + (3 * Math.PI) / 2) +
a);
};
}
};
};
LeafScene.prototype.init = function () {
for (var i = 0; i < this.options.numLeaves; i++) {
var leaf = {
el: document.createElement("div"),
x: 0,
y: 0,
z: 0,
rotation: {
axis: "X",
value: 0,
speed: 0,
x: 0
},
xSpeedVariation: 0,
ySpeed: 0,
path: {
type: 1,
start: 0
},
image: 1
};
this._resetLeaf(leaf);
this.leaves.push(leaf);
this.world.appendChild(leaf.el);
}
this.world.className = "leaf-scene";
this.viewport.appendChild(this.world);
// reset window height/width on resize
var self = this;
window.onresize = function (event) {
self.width = self.viewport.offsetWidth;
self.height = self.viewport.offsetHeight;
};
};
LeafScene.prototype.render = function () {
this._updateWind();
for (var i = 0; i < this.leaves.length; i++) {
this._updateLeaf(this.leaves[i]);
}
this.timer++;
requestAnimationFrame(this.render.bind(this));
};
// start up leaf scene
var leafContainer = document.querySelector(".falling-leaves"), leaves = new LeafScene(leafContainer);
leaves.init();
leaves.render();
You can create a file like <script> ..that js code.. </script> and save it as script.js and then add it to your index.html or dynamicly load it:
public loadScript(){
return new Promise(resolve => {
const scriptElement = document.createElement('script');
scriptElement.src = '/assets/js/script.js'
scriptElement.onload = resolve;
document.body.appendChild(scriptElement);
});
}
and then call and subscribe this function in your ts file to know when it is loaded or do something when its loaded in your page.
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?
I have this working canvas javascript animation but i would like to use it multiple times, currently it's only possible to have one canvas element with the id "stars" and use that one. Could i perhaps add a class for them instead and get the elements class and loop or what would be my best solution for achieving this? I would like to make this work without repeating to much since i could end up using the animation on different pages.
// Settings
var particleCount = 40,
flareCount = 0,
motion = 0.05,
tilt = 0.05,
color = '#00FF7B',
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
lineWidth = 1,
linkChance = 75, // chance per frame of link, higher = smaller chance
linkLengthMin = 5, // min linked vertices
linkLengthMax = 7, // max linked vertices
linkOpacity = 0.25; // number between 0 & 1
linkFade = 90, // link fade-out frames
linkSpeed = 0, // distance a link travels in 1 frame
glareAngle = -60,
glareOpacityMultiplier = 0.4,
renderParticles = true,
renderParticleGlare = true,
renderFlares = false,
renderLinks = false,
renderMesh = false,
flicker = false,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 3;
var canvas = document.getElementById('stars'),
context = canvas.getContext('2d'),
mouse = {
x: 0,
y: 0
},
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 0.5,
nPos = {
x: 0,
y: 0
},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x * c, p.y * c]);
}
vertices = Delaunay.triangulate(points);
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 3) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
var fps = 15;
var now;
var then = Date.now();
var interval = 1000 / fps;
var delta;
// Animation loop
(function animloop() {
requestAnimFrame(animloop);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
resize();
render();
}
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0, 4);
this.color = color;
this.opacity = random(0.1, 1, true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Flare class
// Link class
var Link = function(startVertex, numPoints) {
this.length = numPoints;
this.verts = [startVertex];
this.stage = 0;
this.linked = [startVertex];
this.distances = [];
this.traveled = 0;
this.fade = 0;
this.finished = false;
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
background: #000;
background-image: linear-gradient(-180deg, rgba(0, 0, 0, 0.00) 0%, #000000 100%);
}
#stars {
display: block;
position: relative;
width: 100%;
height: 100%;
z-index: 1;
position: absolute;
}
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
<script src="http://requirejs.org/docs/release/2.1.15/minified/require.js"></script>
<canvas id="stars" width="300" height="300"></canvas>
id="identifier" is unique
class="identifier" could be shared by a list of items
As you mention using class could be an option but you'll need to change your code to select all elements by that class before:
$(".identifier").each(function(a,b)
{
// Actions for each element
}
With javascript:
var elementList = document.getElementsByClassName("identifier");
var elementListSize=elementList.length;
for(var i=0;i<elementListSize;i++) {
// Actions for each element (elementList[i])
}
Having to give the idea of an ever changing network of nodes (each with different impact and possibly more than one color) connecting each other to create something.
I wanted to give it depth perception, so I ended up using two canvases around the title: one in the foreground, even over the words, and the other in background, with slightly larger and blurred elements.
Demo here, full JavaScript code at the moment:
// min and max radius, radius threshold and percentage of filled circles
var radMin = 5,
radMax = 125,
filledCircle = 60, //percentage of filled circles
concentricCircle = 30, //percentage of concentric circles
radThreshold = 25; //IFF special, over this radius concentric, otherwise filled
//min and max speed to move
var speedMin = 0.3,
speedMax = 2.5;
//max reachable opacity for every circle and blur effect
var maxOpacity = 0.6;
//default palette choice
var colors = ['52,168,83', '117,95,147', '199,108,23', '194,62,55', '0,172,212', '120,120,120'],
bgColors = ['52,168,83', '117,95,147', '199,108,23', '194,62,55', '0,172,212', '120,120,120'],
circleBorder = 10,
backgroundLine = bgColors[0];
var backgroundMlt = 0.85;
//min distance for links
var linkDist = Math.min(canvas.width, canvas.height) / 2.4,
lineBorder = 2.5;
//most importantly: number of overall circles and arrays containing them
var maxCircles = 12,
points = [],
pointsBack = [];
//populating the screen
for (var i = 0; i < maxCircles * 2; i++) points.push(new Circle());
for (var i = 0; i < maxCircles; i++) pointsBack.push(new Circle(true));
//experimental vars
var circleExp = 1,
circleExpMax = 1.003,
circleExpMin = 0.997,
circleExpSp = 0.00004,
circlePulse = false;
//circle class
function Circle(background) {
//if background, it has different rules
this.background = (background || false);
this.x = randRange(-canvas.width / 2, canvas.width / 2);
this.y = randRange(-canvas.height / 2, canvas.height / 2);
this.radius = background ? hyperRange(radMin, radMax) * backgroundMlt : hyperRange(radMin, radMax);
this.filled = this.radius < radThreshold ? (randint(0, 100) > filledCircle ? false : 'full') : (randint(0, 100) > concentricCircle ? false : 'concentric');
this.color = background ? bgColors[randint(0, bgColors.length - 1)] : colors[randint(0, colors.length - 1)];
this.borderColor = background ? bgColors[randint(0, bgColors.length - 1)] : colors[randint(0, colors.length - 1)];
this.opacity = 0.05;
this.speed = (background ? randRange(speedMin, speedMax) / backgroundMlt : randRange(speedMin, speedMax)); // * (radMin / this.radius);
this.speedAngle = Math.random() * 2 * Math.PI;
this.speedx = Math.cos(this.speedAngle) * this.speed;
this.speedy = Math.sin(this.speedAngle) * this.speed;
var spacex = Math.abs((this.x - (this.speedx < 0 ? -1 : 1) * (canvas.width / 2 + this.radius)) / this.speedx),
spacey = Math.abs((this.y - (this.speedy < 0 ? -1 : 1) * (canvas.height / 2 + this.radius)) / this.speedy);
this.ttl = Math.min(spacex, spacey);
};
Circle.prototype.init = function() {
Circle.call(this, this.background);
}
//support functions
//generate random int a<=x<=b
function randint(a, b) {
return Math.floor(Math.random() * (b - a + 1) + a);
}
//generate random float
function randRange(a, b) {
return Math.random() * (b - a) + a;
}
//generate random float more likely to be close to a
function hyperRange(a, b) {
return Math.random() * Math.random() * Math.random() * (b - a) + a;
}
//rendering function
function drawCircle(ctx, circle) {
//circle.radius *= circleExp;
var radius = circle.background ? circle.radius *= circleExp : circle.radius /= circleExp;
ctx.beginPath();
ctx.arc(circle.x, circle.y, radius * circleExp, 0, 2 * Math.PI, false);
ctx.lineWidth = Math.max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax));
ctx.strokeStyle = ['rgba(', circle.borderColor, ',', circle.opacity, ')'].join('');
if (circle.filled == 'full') {
ctx.fillStyle = ['rgba(', circle.borderColor, ',', circle.background ? circle.opacity * 0.8 : circle.opacity, ')'].join('');
ctx.fill();
ctx.lineWidth=0;
ctx.strokeStyle = ['rgba(', circle.borderColor, ',', 0, ')'].join('');
}
ctx.stroke();
if (circle.filled == 'concentric') {
ctx.beginPath();
ctx.arc(circle.x, circle.y, radius / 2, 0, 2 * Math.PI, false);
ctx.lineWidth = Math.max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax));
ctx.strokeStyle = ['rgba(', circle.color, ',', circle.opacity, ')'].join('');
ctx.stroke();
}
circle.x += circle.speedx;
circle.y += circle.speedy;
if (circle.opacity < (circle.background ? maxOpacity : 1)) circle.opacity += 0.01;
circle.ttl--;
}
//initializing function
function init() {
window.requestAnimationFrame(draw);
}
//rendering function
function draw() {
if (circlePulse) {
if (circleExp < circleExpMin || circleExp > circleExpMax) circleExpSp *= -1;
circleExp += circleExpSp;
}
var ctxfr = document.getElementById('canvas').getContext('2d');
var ctxbg = document.getElementById('canvasbg').getContext('2d');
ctxfr.globalCompositeOperation = 'destination-over';
ctxfr.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
ctxbg.globalCompositeOperation = 'destination-over';
ctxbg.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
ctxfr.save();
ctxfr.translate(canvas.width / 2, canvas.height / 2);
ctxbg.save();
ctxbg.translate(canvas.width / 2, canvas.height / 2);
//function to render each single circle, its connections and to manage its out of boundaries replacement
function renderPoints(ctx, arr) {
for (var i = 0; i < arr.length; i++) {
var circle = arr[i];
//checking if out of boundaries
if (circle.ttl<0) {}
var xEscape = canvas.width / 2 + circle.radius,
yEscape = canvas.height / 2 + circle.radius;
if (circle.ttl < -20) arr[i].init(arr[i].background);
//if (Math.abs(circle.y) > yEscape || Math.abs(circle.x) > xEscape) arr[i].init(arr[i].background);
drawCircle(ctx, circle);
}
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length; j++) {
var deltax = arr[i].x - arr[j].x;
var deltay = arr[i].y - arr[j].y;
var dist = Math.pow(Math.pow(deltax, 2) + Math.pow(deltay, 2), 0.5);
//if the circles are overlapping, no laser connecting them
if (dist <= arr[i].radius + arr[j].radius) continue;
//otherwise we connect them only if the dist is < linkDist
if (dist < linkDist) {
var xi = (arr[i].x < arr[j].x ? 1 : -1) * Math.abs(arr[i].radius * deltax / dist);
var yi = (arr[i].y < arr[j].y ? 1 : -1) * Math.abs(arr[i].radius * deltay / dist);
var xj = (arr[i].x < arr[j].x ? -1 : 1) * Math.abs(arr[j].radius * deltax / dist);
var yj = (arr[i].y < arr[j].y ? -1 : 1) * Math.abs(arr[j].radius * deltay / dist);
ctx.beginPath();
ctx.moveTo(arr[i].x + xi, arr[i].y + yi);
ctx.lineTo(arr[j].x + xj, arr[j].y + yj);
var samecolor = arr[i].color == arr[j].color;
ctx.strokeStyle = ["rgba(", arr[i].borderColor, ",", Math.min(arr[i].opacity, arr[j].opacity) * ((linkDist - dist) / linkDist), ")"].join("");
ctx.lineWidth = (arr[i].background ? lineBorder * backgroundMlt : lineBorder) * ((linkDist - dist) / linkDist); //*((linkDist-dist)/linkDist);
ctx.stroke();
}
}
}
}
var startTime = Date.now();
renderPoints(ctxfr, points);
renderPoints(ctxbg, pointsBack);
deltaT = Date.now() - startTime;
ctxfr.restore();
ctxbg.restore();
window.requestAnimationFrame(draw);
}
init();
I asked around and ctx.save() and ctx.restore() are in the top list of suspects, but I wouldn't know how to do this without them.
This is my first animation with canvas, which AFAIK should have been the best option in terms of cross-browser support and (decent) performances, but any advice on this side is still welcome; also, seems to slow down significantly on FF, but just on some machines where hardware acceleration does not work properly (or at all).
From what I read here (and basically everywhere else), FF seems to have serious issues dealing with canvas, but maybe I can optimize things a bit more.
Should I use something other than canvas to do the animation? But also consider that other options (like using SVG) seem to have less support, not to mention it would mean redoing most of the work.
Notes: The first part with the general variables might not be the best practice, but it worked to let a non-technical staff member (UI designer) play on the variables to see different results.
I'm trying to replicate this "blackhole" effect in Javascript as seen on i-remember.fr but I am not getting anywhere NEAR as good performance as them, what am I doing wrong? I used to have a jQuery selector in my code that ran every animationframe, but I have that removed now which helps out a bunch. But I'm still running at 40 FPS when their FPS is ridiculously low!
See their timeline
And here is mine
I would never be able to use mine in actual practice because of the framerate! Any ideas on what type of sorcery they are using to increase the framerate?
My Current Engine
View it on CodePen
Javascript
/*$('.blackhole').click(function() {
$(this).toggleClass('open_blackhole');
$(this).toggleClass('close_blackhole');
});*/
// Define Apparatus Variables.
var cw = window.innerWidth,
ch = window.innerHeight,
blackhole_entities = {},
blackhole_entitiesIndex = 0,
blackhole_entitieAmount = 12000, //6000
blackhole_button = $('.blackhole'),
canvas = $('<canvas/>').attr({
width: cw,
height: ch,
id: "apparatus"
}).appendTo('body'),
context = canvas.get(0).getContext("2d");
var requestframe = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
// IE Fallback, you can even fallback to onscroll
function(callback) {
window.setTimeout(callback, 1000 / 60)
};
// Default Entity "Class"
apparatus.blackhole = function(orbit) {
blackhole_entitiesIndex++;
this.id = blackhole_entitiesIndex;
blackhole_entities[blackhole_entitiesIndex] = this;
this.width = .5;
this.height = .5;
this.orbit = orbit;
this.velocity = Math.floor((Math.random() * 3200) + 2500);
this.angle = (Math.PI * 2 / this.width) * Math.floor((Math.random() * cw*4) + 10);;
var choice = Math.random() * 5;
var rands = [];
rands.push(Math.random() * 100 + 1);
rands.push(Math.random() * 10 + 241);
var choice2 = Math.random() * 4;
var rands2 = [];
rands2.push(Math.random() * 100 + 1);
rands2.push(Math.random() * 180 + 211);
this.distance = (rands.reduce(function(p, c) {
return p + c;
}, 0) / rands.length);
this.distance2 = (rands2.reduce(function(p, c) {
return p + c;
}, 0) / rands2.length);
this.increase = Math.PI * 2 / this.width;
this.distancefix = this.distance;
this.distance2fix = this.distance2;
this.color = "255,255,255";
this.alpha = 0.6
this.bx = Math.random() * 20 + 1;
this.by = Math.random() * 20 + 1;
this.inplace = true;
}
apparatus.blackhole.prototype.draw = function() {
if (this.orbit >= 2) {
this.x = this.bx + this.distance * Math.cos(this.angle / this.velocity) + cw / 2;
this.y = this.by + this.distance * Math.sin(this.angle / this.velocity) + ch / 2;
this.alpha = 0.6;
} else {
this.x = this.bx + this.distance2 * Math.cos(this.angle / this.velocity) + cw / 2;
this.y = this.by + this.distance2 * Math.sin(this.angle / this.velocity) + ch / 2;
this.alpha = 0.4;
}/*
if (blackhole_button.hasClass('open_blackhole')) {
blackhole_button.removeClass('close_blackhole');
if (this.distance >= 171) {
this.distance = this.distance - 4;
} else if (this.distance <= 161) {
this.distance= this.distance + 8;
}
if (this.distance2 >= 201) {
this.distance2 = this.distance2 - 6;
} else if (this.distance2 <= 161) {
this.distance2 = this.distance2 + 6;
}
}
if(blackhole_button.hasClass('close_blackhole')){
if (this.distance >= this.distancefix + 4) {
this.distance = this.distance - 4;
} else if (this.distance <= this.distancefix - 5) {
this.distance= this.distance + 5;
}
if (this.distance2 >= this.distance2fix + 10) {
this.distance2 = this.distance2 - 4;
} else if (this.distance2 <= this.distance2fix - 10) {
this.distance2 = this.distance2 + 4;
}
}*/
this.angle += this.increase;
context.fillStyle = "rgba(" + this.color + "," + this.alpha + ")";
context.fillRect(this.x, this.y, this.width, this.height);
}
apparatus.start = function() {
apparatus('true');
}
apparatus.stop = function() {
apparatus('false');
}
for (var i = 0; i < blackhole_entitieAmount; i++) {
new apparatus.blackhole((Math.random() * 5));
}
var mode;
apparatus.spawn_blackhole = function(){
for (i in blackhole_entities) {
blackhole_entities[i].draw();
}
}
function apparatus(mode) {
if (mode == 'true') {
var i;
requestframe(function() {
context.clearRect(0, 0, cw, ch);
apparatus.spawn_blackhole();
apparatus('true');
});
}
}
apparatus.start();
Hopefully someone will have an idea on how I can do this. I am simply trying to further my knowledge in Javascript canvas manipulation.
Thanks a bunch for any tips you can throw my way!
This question might be marked for being a duplicate of my last question, but my last question didn't give me a real, usable framerate. And due to the fact that the person did "answer" my question, I'm here now.