// import { KawaseBlurFilter } from '@pixi/filter-kawase-blur';
import debounce from 'debounce';
import * as PIXI from 'pixi.js';
import SimplexNoise from 'simplex-noise';
/**
*
* Background Orbs
*
*/
// return a random number within a range
function random(min, max) {
return Math.random() * (max - min) + min;
}
// map a number from 1 range to another
function map(n, start1, end1, start2, end2) {
return ((n - start1) / (end1 - start1)) * (end2 - start2) + start2;
}
// Create a new simplex noise instance
const simplex = new SimplexNoise();
// ColorPalette class
class ColorPalette {
constructor() {
this.setColors();
// this.setCustomProperties();
}
setColors() {
// define a base colors
this.color1 = 0xffbc6d;
this.color2 = 0xbc87bd;
this.color3 = 0x68a3cc;
// store the color choices in an array so that a random one can be picked later
this.colorChoices = [this.color1, this.color2, this.color3];
}
randomColor() {
// pick a random color
return this.colorChoices[~~random(0, this.colorChoices.length)];
}
}
// Orb class
// how quickly the noise/self similar random values step through time
const inc = 0.001;
// how large the orb is vs it's original radius (this will modulate over time)
const scale = 0.5;
class Orb {
// Pixi takes hex colors as hexidecimal literals (0x rather than a string with '#')
constructor(fill = 0x000000) {
// bounds = the area an orb is "allowed" to move within
this.bounds = this.setBounds();
// initialise the orb's { x, y } values to a random point within it's bounds
this.x = random(this.bounds['x'].min, this.bounds['x'].max);
this.y = random(this.bounds['y'].min, this.bounds['y'].max);
// how large the orb is vs it's original radius (this will modulate over time)
this.scale = scale;
// what color is the orb?
this.fill = fill;
// the original radius of the orb, set relative to window height
this.radius = random(window.innerHeight / 6, window.innerHeight / 3);
// starting points in "time" for the noise/self similar random values
this.xOff = random(0, 1000);
this.yOff = random(0, 1000);
// PIXI.Graphics is used to draw 2d primitives (in this case a circle) to the canvas
this.graphics = new PIXI.Graphics();
this.graphics.alpha = 0.825;
// 250ms after the last window resize event, recalculate orb positions.
window.addEventListener(
'resize',
debounce(() => {
this.bounds = this.setBounds();
}, 250)
);
}
setBounds() {
// how far from the { x, y } origin can each orb move
const maxDistX = window.innerWidth < 1000 ? window.innerHeight / 2 : window.innerWidth * 0.2;
const maxDistY = window.innerWidth < 1000 ? window.innerHeight / 2 : window.innerHeight * 0.8;
// the { x, y } origin for each orb (the bottom right of the screen)
const originX = window.innerWidth / window.innerWidth;
// const originX = window.innerWidth / 1.25;
const originY = window.innerWidth < 1000 ? window.innerHeight : window.innerHeight;
// const originY = window.innerWidth < 1000 ? window.innerHeight : window.innerHeight / 1.375;
// allow each orb to move x distance away from it's x / y origin
return {
x: {
min: originX - maxDistX,
max: originX + maxDistX,
},
y: {
min: originY - maxDistY,
max: originY + maxDistY,
},
};
}
update() {
// self similar "psuedo-random" or noise values at a given point in "time"
const xNoise = simplex.noise2D(this.xOff, this.xOff);
const yNoise = simplex.noise2D(this.yOff, this.yOff);
const scaleNoise = simplex.noise2D(this.xOff, this.yOff);
// map the xNoise/yNoise values (between -1 and 1) to a point within the orb's bounds
this.x = map(xNoise, -1, 1, this.bounds['x'].min, this.bounds['x'].max);
this.y = map(yNoise, -1, 1, this.bounds['y'].min, this.bounds['y'].max);
// map scaleNoise (between -1 and 1) to a scale value somewhere between half of the orb's original size, and 100% of it's original size
this.scale = map(scaleNoise, -1, 1, 0.5, 1);
// step through "time"
this.xOff += inc;
this.yOff += inc;
}
render() {
// update the PIXI.Graphics position and scale values
this.graphics.x = this.x;
this.graphics.y = this.y;
this.graphics.scale.set(this.scale);
// clear anything currently drawn to graphics
this.graphics.clear();
// tell graphics to fill any shapes drawn after this with the orb's fill color
this.graphics.beginFill(this.fill);
// draw a circle at { 0, 0 } with it's size set by this.radius
this.graphics.drawCircle(0, 0, this.radius);
// let graphics know we won't be filling in any more shapes
this.graphics.endFill();
}
}
class Orb2 {
// Pixi takes hex colors as hexidecimal literals (0x rather than a string with '#')
constructor(fill = 0x000000) {
// bounds = the area an orb is "allowed" to move within
this.bounds = this.setBounds();
// initialise the orb's { x, y } values to a random point within it's bounds
this.x = random(this.bounds['x'].min, this.bounds['x'].max);
this.y = random(this.bounds['y'].min, this.bounds['y'].max);
// how large the orb is vs it's original radius (this will modulate over time)
this.scale = scale;
// what color is the orb?
this.fill = fill;
// the original radius of the orb, set relative to window height
this.radius = random(window.innerHeight / 6, window.innerHeight / 3);
// starting points in "time" for the noise/self similar random values
this.xOff = random(0, 1000);
this.yOff = random(0, 1000);
// PIXI.Graphics is used to draw 2d primitives (in this case a circle) to the canvas
this.graphics = new PIXI.Graphics();
this.graphics.alpha = 0.825;
// 250ms after the last window resize event, recalculate orb positions.
window.addEventListener(
'resize',
debounce(() => {
this.bounds = this.setBounds();
}, 250)
);
}
setBounds() {
// how far from the { x, y } origin can each orb move
const maxDistX = window.innerWidth < 1000 ? window.innerHeight / 2 : window.innerWidth * 0.2;
const maxDistY = window.innerWidth < 1000 ? window.innerHeight / 2 : window.innerHeight * 0.8;
// the { x, y } origin for each orb (the bottom right of the screen)
const originX = window.innerWidth;
// const originX = window.innerWidth / 1.25;
const originY =
window.innerWidth < 1000 ? window.innerHeight : window.innerHeight / window.innerHeight;
// const originY = window.innerWidth < 1000 ? window.innerHeight : window.innerHeight / 1.375;
// allow each orb to move x distance away from it's x / y origin
return {
x: {
min: originX - maxDistX,
max: originX + maxDistX,
},
y: {
min: originY - maxDistY,
max: originY + maxDistY,
},
};
}
update() {
// self similar "psuedo-random" or noise values at a given point in "time"
const xNoise = simplex.noise2D(this.xOff, this.xOff);
const yNoise = simplex.noise2D(this.yOff, this.yOff);
const scaleNoise = simplex.noise2D(this.xOff, this.yOff);
// map the xNoise/yNoise values (between -1 and 1) to a point within the orb's bounds
this.x = map(xNoise, -1, 1, this.bounds['x'].min, this.bounds['x'].max);
this.y = map(yNoise, -1, 1, this.bounds['y'].min, this.bounds['y'].max);
// map scaleNoise (between -1 and 1) to a scale value somewhere between half of the orb's original size, and 100% of it's original size
this.scale = map(scaleNoise, -1, 1, 0.5, 1);
// step through "time"
this.xOff += inc;
this.yOff += inc;
}
render() {
// update the PIXI.Graphics position and scale values
this.graphics.x = this.x;
this.graphics.y = this.y;
this.graphics.scale.set(this.scale);
// clear anything currently drawn to graphics
this.graphics.clear();
// tell graphics to fill any shapes drawn after this with the orb's fill color
this.graphics.beginFill(this.fill);
// draw a circle at { 0, 0 } with it's size set by this.radius
this.graphics.drawCircle(0, 0, this.radius);
// let graphics know we won't be filling in any more shapes
this.graphics.endFill();
}
}
// Create PixiJS app
const app = new PIXI.Application({
// render to
view: document.querySelector('.orb-canvas'),
// auto adjust size to fit the current window
resizeTo: window,
// transparent background, we will be creating a gradient background later using CSS
backgroundAlpha: 0,
});
// Create colour palette
const colorPalette = new ColorPalette();
// app.stage.filters = [new KawaseBlurFilter(30, 10, true)];
// Create orbs
const orbs = [];
for (let i = 0; i < 10; i++) {
const orb = new Orb(colorPalette.randomColor());
const orb2 = new Orb2(colorPalette.randomColor());
app.stage.addChild(orb.graphics);
app.stage.addChild(orb2.graphics);
orbs.push(orb);
orbs.push(orb2);
}
// Animate!
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
app.ticker.add(() => {
orbs.forEach((orb) => {
orb.update();
orb.render();
});
});
} else {
orbs.forEach((orb) => {
orb.update();
orb.render();
});
}
/**
*
* Background Triangles
*
*/
const app2 = new PIXI.Application({
// render to
view: document.querySelector('.webgl'),
width: window.innerWidth,
height: document.body.clientHeight,
backgroundAlpha: 0,
resizeTo: document.querySelector('.section'),
});
const particles = window.innerWidth < 480 ? 50 : 100;
const sprites = new PIXI.ParticleContainer(particles, {
scale: true,
position: true,
rotation: true,
uvs: true,
alpha: true,
});
sprites.alpha = 0.7;
app2.stage.addChild(sprites);
const texture = new PIXI.BaseTexture(
'https://uploads-ssl.webflow.com/62eb7963f3e5753dd31d5fc0/63062c3047ed911ef11b2641_spritesheet.png'
);
const T1 = new PIXI.Texture(texture, new PIXI.Rectangle(1, 1, 41, 43));
const T2 = new PIXI.Texture(texture, new PIXI.Rectangle(44, 1, 72, 49));
const T3 = new PIXI.Texture(texture, new PIXI.Rectangle(1, 52, 42, 43));
const T4 = new PIXI.Texture(texture, new PIXI.Rectangle(45, 52, 51, 46));
const T5 = new PIXI.Texture(texture, new PIXI.Rectangle(118, 1, 70, 56));
const T6 = new PIXI.Texture(texture, new PIXI.Rectangle(98, 59, 55, 75));
// app2.loader
// .add(
// 'trianlges',
// 'https://uploads-ssl.webflow.com/62eb7963f3e5753dd31d5fc0/63062c3047ed911ef11b2641_spritesheet.png'
// )
// .load(onAssetsLoaded);
const triangleFrames = [T1, T2, T3, T4, T5, T6];
const maggots = [];
const totalSprites = app2.renderer instanceof PIXI.Renderer ? 10000 : 100;
for (let i = 0; i < totalSprites; i++) {
// create a new Sprite
const frameName = triangleFrames[i % 6];
const dude = PIXI.Sprite.from(frameName);
// set the anchor point so the texture is centerd on the sprite
dude.anchor.set(0.5);
// different maggots, different sizes
dude.scale.set(0.8 + Math.random() * 0.3);
// scatter them all
dude.x = Math.random() * app2.screen.width;
dude.y = Math.random() * app2.screen.height;
// dude.tint = Math.random() * 0x808080;
// create a random direction in radians
dude.direction = Math.random() * Math.PI * 2;
// this number will be used to modify the direction of the sprite over time
dude.turningSpeed = Math.random() - 0.8;
// create a random speed between 0 - 2, and these maggots are slooww
dude.speed = (1 + Math.random() * 1) * 0.2;
dude.offset = Math.random() * 100;
// finally we push the dude into the maggots array so it it can be easily accessed later
maggots.push(dude);
sprites.addChild(dude);
}
// create a bounding box box for the little maggots
const dudeBoundsPadding = 20;
const dudeBounds = new PIXI.Rectangle(
-dudeBoundsPadding,
-dudeBoundsPadding,
app2.screen.width + dudeBoundsPadding * 2,
app2.screen.height + dudeBoundsPadding * 2
);
let tick = 0;
app2.ticker.add(() => {
// iterate through the sprites and update their position
for (let i = 0; i < maggots.length; i++) {
const dude = maggots[i];
// dude.scale.y = 0.95 + Math.sin(tick + dude.offset) * 0.05;
dude.direction += dude.turningSpeed * 0.01;
dude.x += Math.sin(dude.direction) * (dude.speed * dude.scale.y);
dude.y += Math.cos(dude.direction) * (dude.speed * dude.scale.y);
dude.rotation = -dude.direction + Math.PI;
// wrap the maggots
if (dude.x < dudeBounds.x) {
dude.x += dudeBounds.width;
} else if (dude.x > dudeBounds.x + dudeBounds.width) {
dude.x -= dudeBounds.width;
}
if (dude.y < dudeBounds.y) {
dude.y += dudeBounds.height;
} else if (dude.y > dudeBounds.y + dudeBounds.height) {
dude.y -= dudeBounds.height;
}
}
// increment the ticker
tick += 0.1;
});