LOADING...

Preview

Pen ID
Unlock Campus Themeforest adv

 

Code


Change the constants at the top of the JS file to create different effects!
by @kylestetz
CSS
body,
html {
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: 'Andale Mono', monospace;
  font-size: 12px;
  overflow: hidden;
}
#canvas {
  width: 100%;
  height: 100%;
  cursor: default;
}
#clear {
  position: fixed;
  top: 10px;
  left: 10px;
  padding: 4px;
  font-family: 'Andale Mono', monospace;
  color: #257C67;
  outline: none;
  border: 1px solid #257C67;
  background-color: white;
  cursor: pointer;
  user-select: none;
}
#clear:hover {
  border-color: #D66632;
  color: #D66632;
}
.message {
  position: fixed;
  left: 10px;
  bottom: 10px;
  max-width: 50%;
  color: rgba(0, 0, 0, 0.3);
  user-select: none;
  cursor: default;
}
.link {
  display: block;
  position: fixed;
  bottom: 10px;
  right: 10px;
  color: #257C67;
  text-decoration: none;
  user-select: none;
}
.link:hover {
  color: #D66632;
}
JS
// CONSTANTS: CHANGE THESE
// Number of reflected sides
const sides = 7;
// Radius of the dot we're drawing.
const radius = 5;
// (100 - ?) The timing, in ms, of the feedback delay.
// If you bring this number way down you might
// want to turn `fadeout` up to 0.4 or 0.5.
const delay = 1500;
// (0 - 1) how much opacity to apply on each
// frame to clear the screen. Lower = slower,
// higher = faster.
const fadeout = 0.1;
// (0 - 360) Hue, in degrees, of the dot you're
// currently drawing.
const colorStart = 0;
// (0 - ?) How far should the color rotate across
// the reflected sides? 360 = one full rainbow.
const colorSpread = 360;

// SETUP
// We'll use this ID to make the feedback loop erasable.
let lastId = 0;
// Maybe scale the canvas for a retina display.
const scale = window.devicePixelRatio;
// Grab the canvas and set the width and height.
const canvasElement = document.getElementById('canvas');
canvas.width = window.innerWidth * scale;
canvas.height = window.innerHeight * scale;
const context = canvas.getContext('2d');

// An erase function we'll use when the window gets resized.
const erase = () => context.clearRect(0, 0, window.innerWidth * scale, window.innerHeight * scale);

// On resize set the canvas width & height so there's no stretching.
window.onresize = () => {
  canvasElement.width = window.innerWidth * scale;
  canvasElement.height = window.innerHeight * scale;
};

// STREAMS
// A stream of mouse movement.
const mouseMovement = Rx.Observable.fromEvent(document.querySelector('body'), 'mousemove');
// For some reason `fromEvent` isn't working for touchmove so we'll make our own stream.
const touchMovement = new Rx.Subject();
document.addEventListener('touchmove', e => {
  e.stopPropagation();
  touchMovement.next(e);
});
// A special stream that will create a feedback loop,
// continuously processing the same information on
// a delay.
const feedback = new Rx.Subject();
// A stream of clear button clicks.
const clearDrawing = Rx.Observable.fromEvent(document.querySelector('#clear'), 'click');
// A stream of requestAnimationFrame callbacks for doing work at 60fps.
const frames = Rx.Observable.create(observable => {
  const frame = () => {
    window.requestAnimationFrame(frame);
    observable.next();
  }
  frame();
});

// LOGIC
// Using the mouse movement as input we're going to set up
// a feedback loop that endlessly replays our event data.
mouseMovement
  .merge(touchMovement)
  // Only send movement events if the mouse button is pressed or if it's a touch event.
  .filter(e => {
    if (e.targetTouches) return true;
    return e.button === 1 || e.which === 1;
  })
  // We don't want or need to pass the full event object around
  // so we map the stream to only the values we need. We also
  // assign an ID before we enter the feedback loop, which allows
  // us to clear the current drawings by filtering out old IDs.
  .map(e => {
      if (e.targetTouches) return { x: e.targetTouches[0].clientX, y: e.targetTouches[0].clientY, id: lastId };
      return { x: e.clientX, y: e.clientY, id: lastId };
  })
  // Merge the feedback stream into this one, allowing us to
  // replay previously observed events as if they were new.
  .merge(feedback)
  // Now we're in feedback territory! Filter out any events with
  // an old ID. The ID will be incremented using the clear button.
  .filter(e => e.id === lastId)
  // On each event draw the coordinates to the canvas. We're mapping
  // here rather than subscribing because we still have to pipe
  // the data into the feedback loop. If we subscribed we wouldn't
  // be able to further modify the stream.
  .map(e => {
    // We're going to draw the data once for every symmetrical side.
    for(let i = 0; i < sides; i++) {
      // rotate the original coordinates around the center of the canvas.
      const { rx, ry } = rotateAroundCenter(e.x, e.y, (Math.PI * 2) / sides * i);
      // Set the hue based on the current rotation.
      context.fillStyle = `hsl(${colorStart + (colorSpread / sides * i)}, 100%, 50%)`;
      // Draw a circle •
      context.beginPath();
      context.arc(rx * scale, ry * scale, radius, 0, Math.PI * 2);
      context.fill();
    }
    // We're using map so we must return the event.
    return e;
  })
  // Delay the event for the specified number of ms.
  .delay(Math.max(100, delay))
  // Take the delayed event and push it into the feedback stream.
  .subscribe(e => feedback.next(e));

// when the clear button is pressed we increment
// the lastId, filtering out any old events.
clearDrawing.subscribe(e => {
  lastId = lastId + 1;
  erase();
});

// Each frame, fade the canvas out a bit by putting a
// semi-transparent white box over everything. The
// `fadeout` const determines how much opacity to apply.
frames.subscribe(() => {
  context.fillStyle = `rgba(255, 255, 255, ${fadeout})`;
  context.fillRect(0, 0, window.innerWidth * scale, window.innerHeight * scale);
})

// HELPERS
// A function for rotating a point around the center
// of the canvas by `angle` radians. We're working with
// screen coordinates here — not canvas coordinates —
// so we don't apply the `scale`.
function rotateAroundCenter(xpos, ypos, angle) {
  const cx = window.innerWidth / 2;
  const cy = window.innerHeight / 2;
  const rx = Math.cos(angle) * (xpos - cx) - Math.sin(angle) * (ypos - cy) + cx;
  const ry = Math.sin(angle) * (xpos - cx) + Math.cos(angle) * (ypos - cy) + cy;
  return { rx, ry };
}
Host Instantly Drag and Drop Website Builder

 

Description

A kaleidoscopic drawing effect on an infinite loop. Uses RxJS observables plugged into each other to create an infinite feedback loop on a delay. This is the same technique used in scribble.audio.
Term
Mon, 11/27/2017 - 21:44

Related Codes

Pen ID
Pen ID
Pen ID