<h2>Create your own Kaleidoscope video thing</h2>
<p class="instructions">Drag-drop an image onto the canvas to load it into the kaleidoscope. Click on the record button at any time to record the output. Video will download when you stop recording. Move your mouse over the canvas to stretch things. Note: this CodePen works in Chrome and (possibly) Firefox; YMMV!</p>

<div>
  <button id="my-record-video-button">Record a video</button>
</div>

<canvas id="mycanvas" width="600" height="400" data-scrawl-canvas></canvas>

<div id="my-image-store">
  <img src="https://assets.codepen.io/128723/iris.png" id="iris" class="flowers" crossOrigin="anonymous" />
</div>

<p>Learn more on the <a href="https://scrawl-v8.rikweb.org.uk/">Scrawl-canvas v8</a> homepage</p>
body {
  font-family: sans-serif;
  text-align: center;
}
canvas {
  margin: 0 auto;
}
#my-image-store {
  display: none;
}
.instructions {
  max-width: 48rem;
  margin: 0 auto;
}
.button-container {
  width: 100%;
  text-align: center;
}
button {
  font-size: 1.4rem;
  padding: 0.4rem;
  width: 280px;
  margin-top: 1rem;
  background-color: darkred;
  color: yellow;
}
import * as scrawl from "https://unpkg.com/scrawl-canvas@8.15.0";

// Scene setup
const canvas = scrawl.findCanvas('mycanvas'),
  entitys = scrawl.library.entity;

canvas.setBase({
  compileOrder: 1,
  backgroundColor: 'darkslategray',
});

scrawl.importDomImage(".flowers");

// The panel image - built in its own Cell using a Loom entity. The Loom requires two track paths and a Picture entity to generate its output.
canvas.buildCell({
  name: "image-generator",
  dimensions: ["100%", "50%"],
  shown: false
});

scrawl
  .makeBezier({
    name: "left-track",
    group: "image-generator",

    start: [0, 0],
    startControl: [0, "50%"],
    endControl: ["70%", "75%"],
    end: ["50%", "100%"],

    precision: 0.05,

    method: "draw",
    useAsPath: true
  })
  .clone({
    name: "right-track",

    start: ["100%", 0],
    startControl: ["70%", "50%"],
    endControl: ["100%", "75%"]
  });

const backgroundImage = scrawl.makePicture({
  name: "my-background",
  group: "image-generator",
  asset: "iris",

  dimensions: ["100%", "100%"],
  copyStart: ["30%", 0],
  copyDimensions: ["40%", "100%"],

  visibility: false
});

scrawl.makeLoom({
  name: "display-loom",
  group: "image-generator",

  fromPath: "left-track",
  toPath: "right-track",

  synchronizePathCursors: true,
  loopPathCursors: true,
  fromPathStart: 0.001,

  source: "my-background",

  method: "fill",

  delta: {
    fromPathStart: 0.0015,
    fromPathEnd: 0.0015
  }
});

// The main scene comprises a group of Picture entitys positioned using a set of Line entitys which in turn use an Oval entity as a path. We match each Picture entity's height to its Line entity's length to create the mouseover effect
const rods = scrawl.makeGroup({
  name: "rods",
  host: canvas.base.name
});

const panels = scrawl.makeGroup({
  name: "panels",
  host: canvas.base.name
});

scrawl.makeOval({
  name: "rim",
  start: ["center", "center"],
  handle: ["center", "center"],
  radiusX: 450,
  radiusY: 450,
  useAsPath: true,
  delta: {
    roll: 0.2
  },
  method: "draw"
});

let index = 0;
for (let i = 0.05; i < 1; i += 0.1) {
  scrawl.makeLine({
    name: `rod-${index}`,
    group: "rods",

    start: ["center", "center"],
    useStartAsControlPoint: true,

    endPath: "rim",
    endPathPosition: i,
    endLockTo: "path",

    method: "none",

    useAsPath: true
  });

  scrawl.makePicture({
    name: `panel-${index}`,
    group: "panels",

    asset: "image-generator",
    dimensions: [240, 350],
    copyDimensions: ["100%", "100%"],

    handle: ["center", 0],

    roll: 90,

    path: `rod-${index}`,
    pathPosition: 1,
    addPathRotation: true,
    lockTo: "path"
  });

  index++;
}

// Create the Display cycle animation
let mouseCheck = (function () {
  let active = false;

  return function () {
    if (canvas.here.active !== active) {
      active = canvas.here.active;

      rods.setArtefacts({
        lockTo: active ? "mouse" : "start"
      });
    }

    panels.get("artefacts").forEach((name) => {
      let panel = entitys[name];

      if (panel) {
        let rod = entitys[`rod-${Math.floor(parseFloat(name.slice(6)))}`];

        if (rod) {
          panel.set({
            height: rod.length * 0.95
          });
        }
      }
    });
  };
})();

const demoAnimation = scrawl.makeRender({
  name: "demo-animation",
  target: canvas,
  commence: mouseCheck,
});

scrawl.addNativeListener(
  "touchmove",
  (e) => {
    e.preventDefault();
    e.returnValue = false;
  },
  canvas.domElement
);

// Drag-and-Drop image loading functionality
const store = document.querySelector("#my-image-store");
const timeoutDelay = 200;

let counter = 0;

scrawl.addNativeListener(
  ["dragenter", "dragover", "dragleave"],
  (e) => {
    e.preventDefault();
    e.stopPropagation();
  },
  canvas.domElement
);

scrawl.addNativeListener(
  "drop",
  (e) => {
    e.preventDefault();
    e.stopPropagation();

    const dt = e.dataTransfer;

    if (dt) [...dt.files].forEach(addImageAsset);
  },
  canvas.domElement
);

const addImageAsset = (file) => {
  if (file.type.indexOf("image/") === 0) {
    const reader = new FileReader();

    reader.readAsDataURL(file);

    reader.onloadend = function () {
      // Create a name for our new asset
      const name = `user-upload-${counter}`;
      counter++;

      // Add the image to the DOM and create our asset from it
      const img = document.createElement("img");
      img.src = reader.result;
      img.id = name;
      store.appendChild(img);

      scrawl.importDomImage(`#${name}`);

      // Update our Picture entity's asset attribute so it displays the new image
      backgroundImage.set({
        asset: name
      });
    };
  }
};

// Video recording and download functionality
const videoButton = document.querySelector("#my-record-video-button");

let recording = false;
let myRecorder;
let recordedChunks;

videoButton.addEventListener("click", () => {
  recording = !recording;

  if (recording) {
    videoButton.textContent = "Stop recording";

    const stream = canvas.domElement.captureStream(25);

    myRecorder = new MediaRecorder(stream, {
      mimeType: "video/webm;codecs=vp8"
    });

    recordedChunks = [];

    myRecorder.ondataavailable = (e) => {
      if (e.data.size > 0) recordedChunks.push(e.data);
    };

    myRecorder.start();
  } else {
    videoButton.textContent = "Record a video";

    myRecorder.stop();

    setTimeout(() => {
      const blob = new Blob(recordedChunks, { type: "video/webm" });

      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");

      a.href = url;
      a.download = `Scrawl-canvas-art-recording-${Date().slice(4, 24)}.webm`;
      a.click();

      URL.revokeObjectURL(url);
    }, 0);
  }
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.