<script>
  var images = {
    Mario: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAMAAABcgh8DAAAADFBMVEUAAAD/MRjGYwD/lFpY2Im6AAAAAXRSTlMAQObYZgAAAExJREFUeNptzgkKwCAQQ1Enuf+dm8wYbIsfF56CuC6VO0pDACS4tij1DN8WLLUmTltA+Spq5gSmwphEsTRANgXHphXnz7Pk5e929l8Pl0sBHu8kwiYAAAAASUVORK5CYII=",
    Mushroom: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAADFBMVEVHcEzmnCG1MSD//v88rhxeAAAAAXRSTlMAQObYZgAAAENJREFUeNp9jEESACAIAhP//+cManA8tBdlR1kiCm3OwMw28QScZdSjVdDo2CKaOGMIlOgdFAOVJuEHyQuDRXzEankDhM8BIzKiB5cAAAAASUVORK5CYII=",
    Star: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEVHcEzmnCG1MSDkqOjHAAAAAXRSTlMAQObYZgAAADlJREFUeNo1xCEBADAIRcGHIAJ9iIDgCwLQv8LEthMHlnDz+kVDSE3MNj5b2GyCxM2kfHUknlY41AGBagqh/yM9HQAAAABJRU5ErkJggg==",
    Block: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAK0lEQVR4AWP4f+4oSYhhjhcWBAS4xEnTQA7AbyqmOB00kAxoEqyj8UAyAAAI6bN914aCXQAAAABJRU5ErkJggg==",
    "Special Block": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEVHcEycSgDmnCEAAABTL0nuAAAAAXRSTlMAQObYZgAAAEBJREFUeAFjEA0NDWHIWrVqNUPeqlW7GbKmRq1myIq/BiTipsOIfeFAYmU9iKgCEqt+gVhZYC5I26/dEAP+AwEAS28lRmY43mwAAAAASUVORK5CYII="
  };
</script>
.row {
  display: flex;
  font-size: 4vmin;
  gap: 1px;
  margin: 1px;
  input {
    transform: translateZ(0);
    display: block;
    line-height: 1;
    width: 1em;
    height: 1em;
    margin: 0;
  }
}

html {
  height: 100%;
}
body {
  min-height: 100%;
  display: grid;
  place-items: center;
}

.imageSelector {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 4px;
  justify-content: center;
  img {
    image-rendering: pixelated;
    width: 32px;
    cursor: pointer;
    padding: 4px;
    &[data-selected="true"] {
      border: solid 2px;
    }
  }
}
View Compiled
console.clear();

import gsap from "https://cdn.skypack.dev/gsap@3.10.4";
import React, {
  useCallback,
  useEffect,
  useState,
  useRef
} from "https://cdn.skypack.dev/react@17";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17";

function makeRows(image) {
  const { width, height, data } = image;
  const rows = [];
  for (var y = 0; y < height; y++) {
    let row = [];
    rows.push(row);
    let rowi = y * width * 4;
    for (var x = 0; x < width; x++) {
      let i = x * 4 + rowi;
      let checked = data[i + 3] > 0.5;
      let color = `rgba(${data[i]},${data[i + 1]},${data[i + 2]},${
        data[i + 3]
      })`;
      row.push({ checked, color });
    }
  }

  return rows;
}

function CheckboxImage({ image }) {
  const rows = makeRows(image);
  const ref = useRef();
  console.log({ image, rows });

  const animateCheckboxes = useCallback(
    ({ index, from, to, ...options }) => {
      const checkboxes = ref.current.querySelectorAll("input");

      const config = {
        duration: 0.3,
        ease: "elastic.out(2, 0.5)",
        stagger: {
          // wrap advanced options in an object
          each: 0.05,
          from: index,
          grid: "auto",
          ease: "linear.easeNone"
        }
      };

      if (to) {
        return gsap.fromTo(checkboxes, from, { ...config, ...options, ...to });
      }

      return gsap.from(checkboxes, {
        ...config,
        ...options,
        ...from
      });
    },
    [ref]
  );

  function animateClick(index) {
    var tl = gsap.timeline();
    tl.set("body", { "pointer-events": "none" });
    tl.add(
      animateCheckboxes({
        index,
        from: { scale: 1 },
        to: { scale: 0.5, checked: false },
        scale: 1,
        repeat: 1,
        yoyo: true
      })
    );
    tl.set("body", { "pointer-events": "" });
  }

  useEffect(() => {
    var tl = gsap.timeline();
    tl.add([
      animateCheckboxes({ index: "center", from: { checked: false } }),
      animateCheckboxes({
        index: "center",
        from: { scale: 0.5 },
        to: { scale: 1 }
      })
    ]);
  }, []);

  let i = 0;

  return (
    <div className="container" ref={ref}>
      {rows.map((row, rowIndex) => (
        <div key={rowIndex} className="row">
          {row.map(({ checked, color }) => {
            const index = i++;
            return (
              <input
                key={index}
                type="checkbox"
                checked={checked ? "checked" : null}
                onClick={() => animateClick(index)}
                style={{ "accent-color": color }}
              />
            );
          })}
        </div>
      ))}
    </div>
  );
}

let defaultImage = images.Mario;

function App() {
  const [image, setImage] = useState("Mario");
  const [imageData, setImageData] = useState();

  useEffect(() => {
    setImageData(null);
    makeImg(images[image], function (img) {
      const data = parseImg(img);
      setImageData(data);
    });
  }, [image]);

  return (
    <div>
      <div className="imageSelector">
        {Object.entries(images).map(([key, value]) => {
          return (
            <img
              src={value}
              key={key}
              data-selected={image === key}
              onClick={() => setImage(key)}
            />
          );
        })}
      </div>
      {imageData && <CheckboxImage key={image} image={imageData} />}
    </div>
  );
}

const elApp = document.createElement("div");
document.body.appendChild(elApp);
ReactDOM.render(<App />, elApp);

function makeImg(src, callback) {
  const img = new Image();
  img.onload = () => callback(img);
  img.src = src.target ? src.target.result : src;
  return img;
}

async function loadImg(src) {
  const img = new Image();
  // img.crossOrigin = "Anonymous";
  const imgLoaded = new Promise((resolve, reject) => {
    img.onload = () => resolve(img);
    img.onerror = reject;
  });
  img.src = src.target ? src.target.result : src;
  await imgLoaded;
  return img;
}

function parseImg(img) {
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  console.log({ img });

  let biggest = Math.max(img.width, img.height);
  let ratio = biggest > 32 ? 32 / biggest : 1;

  canvas.width = img.width * ratio;
  canvas.height = img.height * ratio;

  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

  return ctx.getImageData(0, 0, canvas.width, canvas.height);
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.