skip to Main Content

Im using a lavalamp javascript effect for a background. I Have also implemented a dark/light mode toggle with CSS media query. The toggle works as expected, switching the colour theme but not the javascript lavalamp colours. Would this be handled in the javascript or in the CSS?

The desired effect would allow lavalamp colours also toggle and be set by the dark mode media query.

https://codepen.io/cleaton/pen/NWJMyGG

Light mode

Dark mode

    // Colour mode controls
const btn = document.querySelector(".btn-toggle");
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");

const currentTheme = localStorage.getItem("theme");
if (currentTheme == "dark") {
  document.body.classList.toggle("dark-theme");
} else if (currentTheme == "light") {
  document.body.classList.toggle("light-theme");
}

btn.addEventListener("click", function () {
  if (prefersDarkScheme.matches) {
    document.body.classList.toggle("light-theme");
    var theme = document.body.classList.contains("light-theme")
      ? "light"
      : "dark";
  } else {
    document.body.classList.toggle("dark-theme");
    var theme = document.body.classList.contains("dark-theme")
      ? "dark"
      : "light";
  }
  localStorage.setItem("theme", theme);
});

// Create a PixiJS application of type cavas with specify background color and make it resizes to the iframe window
const app = new PIXI.Application({
  background: "yellow",
  resizeTo: window
});

// Adding the application's view to the DOM
document.body.appendChild(app.view);

// Basic settings
app.stage.eventMode = "dynamic";
app.stage.hitArea = app.screen;

// Create container for orbs
const container = new PIXI.Container();
app.stage.addChild(container);

// Make a nice color pallete for our random orbs
const colors = ["0x4361ee", "0x3a0ca3", "0x7209b7", "0xf72585"];
const blurFilter2 = new PIXI.BlurFilter();

// Helper Functions

function randomCircle() {
  const circle = new PIXI.Graphics();
  // create random circle
  const randomColor = Math.floor(Math.random() * colors.length);
  circle.beginFill(colors[randomColor]);
  circle.drawCircle(0, 0, (Math.random() * app.screen.width) / 4);
  circle.endFill();
  // generateTexture converts a graphic to a texture, which can be used to
  // create a sprite
  const texture = app.renderer.generateTexture(circle);
  return {
    texture
  };
}

// Orbs
const orbs = [];

// orb vars
let trackSpeed = 0.03;
let rotationSpeed = 0.01;

// set wrapping boundaries (invisible rectangle) to be roughly equal to orb size
const padding = app.screen.width / 4;
const bounds = new PIXI.Rectangle(
  -padding,
  -padding,
  app.screen.width + padding * 2,
  app.screen.height + padding * 2
);

// create 20 orbs with randomized variables
for (let i = 0; i < 20; i++) {
  const orb = PIXI.Sprite.from(randomCircle().texture);

  orb.anchor.set(0.5);
  container.addChild(orb);

  orb.direction = Math.random() * Math.PI * 2;
  orb.speed = 1;
  orb.turnSpeed = Math.random() - 0.8;

  orb.x = Math.random() * bounds.width;
  orb.y = Math.random() * bounds.height;

  orb.scale.set(1 + Math.random() * 0.3);
  orb.original = new PIXI.Point();
  orb.original.copyFrom(orb.scale);

  orbs.push(orb);
}

// Blur the orbs
// This seems like it could get pretty heavy pretty fast, low quality is fast but not very smooth
container.filters = [blurFilter2];
blurFilter2.blur = 300;
blurFilter2.quality = 35;

// Events
// store cursor coords
let mouseX;
let mouseY;

app.stage.on("pointermove", (event) => {
  mouseX = event.global.x;
  mouseY = event.global.y;
});

// Ticker variables
let count = 0;

// Listen for animate update
app.ticker.add((delta) => {
  count += 0.02;

  // animate orbs
  for (let i = 0; i < orbs.length; i++) {
    const orb = orbs[i];

    orb.direction += orb.turnSpeed * 0.01;
    orb.x += Math.sin(orb.direction) * orb.speed;
    orb.y += Math.cos(orb.direction) * orb.speed;

    orb.rotation = -orb.direction - Math.PI / 2;
    orb.scale.x = orb.original.x + Math.sin(count) * 0.2;

    // wrap the orbs around as they hit the bounds
    if (orb.x < bounds.x) {
      orb.x += bounds.width;
    } else if (orb.x > bounds.x + bounds.width) {
      orb.x -= bounds.width;
    }

    if (orb.y < bounds.y) {
      orb.y += bounds.height;
    } else if (orb.y > bounds.y + bounds.height) {
      orb.y -= bounds.height;
    }
  }
});

2

Answers


  1. From a quick test, the CSS filter property appears to work on <canvas> elements.

    In your codepen example, you would add the following:

    body.dark-theme canvas {
      filter: saturate(0);
    }
    @media (prefers-color-scheme: dark) {
      body.light-theme canvas {
        filter: saturate(1);
      }
    }
    

    but… there’s a got’cha: the z-index of the canvas changes when applying the filter. Adding position: absolute; and z-index: -1; will prevent the canvas from covering the page.

    Login or Signup to reply.
  2. Converting my comments into an answer.

    Drawing on a canvas makes it quite hard to alter the colors, you’ll need to

    • Draw Orbs
    • On button click;
      • Remove the orbs
      • Draw them with correct colors

    Since you’ve already placed all the orbs in a container, we can remove the orbs with the following method:

    container.removeChildren();
    

    [docs]


    Then, call the draw logic again, I’ve moved that to a seperate function addOrbs which calls randomCircle as you made, but I’ve added a darkColors array which is chosen over the colors array if theme === 'dark'.

    I’ve removed the animation for this demo to reduce the amount of code.

    Some parts could also still be optimised, but this is just to share the idea.

    Hope it helps.

    let   theme = 'light';
    const toggleTheme = () => (theme = (theme === 'dark' ? 'light' : 'dark'));
    const colors = ["0x4361ee", "0x3a0ca3", "0x7209b7", "0xf72585"];
    const darkColors = ["0x221D00", "0x423800", "0x22001D", "0x22001d"];
    
    const app = new PIXI.Application({
        background: "yellow",
        resizeTo: document.window
    });
    
    const padding = app.screen.width / 4;
    const bounds = new PIXI.Rectangle(
      -padding,
      -padding,
      app.screen.width + padding * 2,
      app.screen.height + padding * 2
    );
    
    const container = new PIXI.Container();
    app.stage.addChild(container);
    document.body.appendChild(app.view);   
    
    function randomCircle() {
      const circle = new PIXI.Graphics();
      const availableColors = (theme === 'dark') ? darkColors : colors;
      // create random circle
      const randomColor = Math.floor(Math.random() * availableColors.length);
      circle.beginFill(availableColors[randomColor]);
      circle.drawCircle(0, 0, (Math.random() * app.screen.width) / 4);
      circle.endFill();
      // generateTexture converts a graphic to a texture, which can be used to
      // create a sprite
      const texture = app.renderer.generateTexture(circle);
      return {
        texture
      };
    }
    
    const addOrbs = (dark = false) => {
            
        app.stage.eventMode = "dynamic";
        app.stage.hitArea = app.screen;
           
        const blurFilter2 = new PIXI.BlurFilter();
        
        const orbs = [];
        let trackSpeed = 0.03;
        let rotationSpeed = 0.01;
        
        for (let i = 0; i < 20; i++) {
          const orb = PIXI.Sprite.from(randomCircle().texture);
    
          orb.anchor.set(0.5);
          container.addChild(orb);
    
          orb.direction = Math.random() * Math.PI * 2;
          orb.speed = 1;
          orb.turnSpeed = Math.random() - 0.8;
    
          orb.x = Math.random() * bounds.width;
          orb.y = Math.random() * bounds.height;
    
          orb.scale.set(1 + Math.random() * 0.3);
          orb.original = new PIXI.Point();
          orb.original.copyFrom(orb.scale);
    
          orbs.push(orb);
        }
    }
    
    const btn = document.querySelector('.btn-toggle');
    btn.addEventListener('click', (e) => {
      toggleTheme();
      container.removeChildren();
      addOrbs();
    });
    
    addOrbs();
    .content {
     position: absolute;
     top: 50%;
     left: 50%;
     transform: translate(-50%, -50%);
    }
    
    
    body {
      --text-color: #222;
      --bkg-color: #fff;
    }
    body.dark-theme {
      --text-color: #eee;
      --bkg-color: #121212;
    }
    
    body, .content {
      background: var(--bkg-color);
    }
    
    h1, p {
      color: var(--text-color);
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/7.2.4/pixi.min.js"></script>
    <div class="content">
      <button class="btn-toggle">Toggle Dark-Mode</button>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search