// 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; });