@manufosela/photo-collage

@manufosela/photo-collage

A photo collage web component with random rotations, polaroid style, image cycling, and zoom on click

Interactive Demo

4x3 grid with 16 images. Toggle options to see the effects.

Photo 1 Photo 2 Photo 3 Photo 4 Photo 5 Photo 6 Photo 7 Photo 8 Photo 9 Photo 10 Photo 11 Photo 12 Photo 13 Photo 14 Photo 15 Photo 16
<photo-collage width="1200" height="700" cols="4" rows="3" max-rotation="15" interval="5" polaroid zoomable randomize > <img src="photo1.jpg" alt="Photo 1"> <img src="photo2.jpg" alt="Photo 2"> <!-- ... more images than cols*rows to enable randomize --> </photo-collage>

Minimal (no polaroid, no zoom)

3x2 grid with subtle rotation and no decorations.

Photo 1 Photo 2 Photo 3 Photo 4 Photo 5 Photo 6
<photo-collage width="600" height="400" cols="3" rows="2" max-rotation="8"> <img src="photo1.jpg" alt="Photo 1"> ... </photo-collage>
Demo code (CodePen-ready HTML, CSS, JS)
HTML (html)
<div class="container">
    

    <div class="demo-card">
      <h2>Interactive Demo</h2>
      <p>4x3 grid with 16 images. Toggle options to see the effects.</p>

      <div class="controls">
        <label>
          Cols:
          <input type="number" id="cols" value="4" min="1" max="10">
        </label>
        <label>
          Rows:
          <input type="number" id="rows" value="3" min="1" max="10">
        </label>
        <label>
          Interval (s):
          <input type="number" id="interval" value="5" min="1" max="30">
        </label>
        <label>
          <input type="checkbox" id="randomize"> Randomize
        </label>
        <label>
          <input type="checkbox" id="shuffle"> Shuffle
        </label>
        <label>
          <input type="checkbox" id="polaroid" checked> Polaroid
        </label>
        <label>
          <input type="checkbox" id="zoomable" checked> Zoomable
        </label>
      </div>

      <div class="demo-content">
        <photo-collage
          id="collage"
          width="1200"
          height="700"
          cols="4"
          rows="3"
          max-rotation="15"
          interval="5"
          polaroid
          zoomable
        >
          <img src="https://picsum.photos/seed/c01/600/400" alt="Photo 1">
          <img src="https://picsum.photos/seed/c02/600/400" alt="Photo 2">
          <img src="https://picsum.photos/seed/c03/600/400" alt="Photo 3">
          <img src="https://picsum.photos/seed/c04/600/400" alt="Photo 4">
          <img src="https://picsum.photos/seed/c05/600/400" alt="Photo 5">
          <img src="https://picsum.photos/seed/c06/600/400" alt="Photo 6">
          <img src="https://picsum.photos/seed/c07/600/400" alt="Photo 7">
          <img src="https://picsum.photos/seed/c08/600/400" alt="Photo 8">
          <img src="https://picsum.photos/seed/c09/600/400" alt="Photo 9">
          <img src="https://picsum.photos/seed/c10/600/400" alt="Photo 10">
          <img src="https://picsum.photos/seed/c11/600/400" alt="Photo 11">
          <img src="https://picsum.photos/seed/c12/600/400" alt="Photo 12">
          <img src="https://picsum.photos/seed/c13/600/400" alt="Photo 13">
          <img src="https://picsum.photos/seed/c14/600/400" alt="Photo 14">
          <img src="https://picsum.photos/seed/c15/600/400" alt="Photo 15">
          <img src="https://picsum.photos/seed/c16/600/400" alt="Photo 16">
        </photo-collage>
      </div>

      <div class="code-block">&lt;photo-collage
  width="1200" height="700"
  cols="4" rows="3"
  max-rotation="15"
  interval="5"
  polaroid zoomable randomize
&gt;
  &lt;img src="photo1.jpg" alt="Photo 1"&gt;
  &lt;img src="photo2.jpg" alt="Photo 2"&gt;
  &lt;!-- ... more images than cols*rows to enable randomize --&gt;
&lt;/photo-collage&gt;</div>
    </div>

    <div class="demo-card">
      <h2>Minimal (no polaroid, no zoom)</h2>
      <p>3x2 grid with subtle rotation and no decorations.</p>

      <div class="demo-content">
        <photo-collage
          width="600"
          height="400"
          cols="3"
          rows="2"
          max-rotation="8"
        >
          <img src="https://picsum.photos/seed/m01/600/400" alt="Photo 1">
          <img src="https://picsum.photos/seed/m02/600/400" alt="Photo 2">
          <img src="https://picsum.photos/seed/m03/600/400" alt="Photo 3">
          <img src="https://picsum.photos/seed/m04/600/400" alt="Photo 4">
          <img src="https://picsum.photos/seed/m05/600/400" alt="Photo 5">
          <img src="https://picsum.photos/seed/m06/600/400" alt="Photo 6">
        </photo-collage>
      </div>

      <div class="code-block">&lt;photo-collage width="600" height="400" cols="3" rows="2" max-rotation="8"&gt;
  &lt;img src="photo1.jpg" alt="Photo 1"&gt;
  ...
&lt;/photo-collage&gt;</div>
    </div>

    
  </div>
CSS (css)
:root {
  --bg: #0c0f14;
  --bg-elevated: #141923;
  --bg-panel: #171d28;
  --border: #262f3f;
  --text: #f4f6fb;
  --text-muted: #a7b0c2;
  --text-dim: #7d879b;
  --accent: #ff8a3d;
  --accent-strong: #ff6a00;
  --accent-soft: rgba(255, 138, 61, 0.16);
  --shadow: 0 20px 50px rgba(5, 8, 14, 0.45);
  --radius-lg: 22px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --max-width: 1160px;
}

:root {
      --bg-color: #f5f5f7;
      --card-bg: #ffffff;
      --text-color: #1d1d1f;
      --text-muted: #86868b;
      --border-color: #d2d2d7;
    }

    * { box-sizing: border-box; margin: 0; padding: 0; }

    .container {
      max-width: 1300px;
      margin: 0 auto;
    }

    header {
      text-align: center;
      margin-bottom: 3rem;
    }

    header h1 {
      font-size: 2.5rem;
      font-weight: 700;
      margin-bottom: 0.5rem;
    }

    .subtitle {
      color: var(--text-muted);
      font-size: 1.1rem;
      margin-bottom: 0.75rem;
    }

    header p a {
      color: var(--text-muted);
      text-decoration: none;
    }

    header p a:hover {
      text-decoration: underline;
    }

    .demo-card {
      background: var(--card-bg);
      border: 1px solid var(--border-color);
      border-radius: 16px;
      padding: 2rem;
      margin-bottom: 2rem;
      box-shadow: 0 1px 3px rgba(0,0,0,0.06);
    }

    .demo-card h2 {
      font-size: 1.3rem;
      margin-bottom: 0.5rem;
    }

    .demo-card > p {
      color: var(--text-muted);
      margin-bottom: 1.5rem;
      font-size: 0.95rem;
    }

    .demo-content {
      display: flex;
      justify-content: center;
      margin-bottom: 1.5rem;
      overflow: auto;
    }

    .controls {
      display: flex;
      gap: 16px;
      margin-bottom: 1.5rem;
      flex-wrap: wrap;
      justify-content: center;
    }

    .controls label {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 13px;
      color: var(--text-muted);
    }

    .controls input[type="checkbox"] {
      accent-color: #007aff;
    }

    .controls input[type="number"] {
      border: 1px solid var(--border-color);
      padding: 4px 8px;
      border-radius: 6px;
      font-size: 13px;
      width: 60px;
    }

    .code-block {
      background: #1d1d1f;
      color: #f5f5f7;
      padding: 1rem 1.25rem;
      border-radius: 8px;
      font-family: 'SF Mono', Monaco, monospace;
      font-size: 0.85rem;
      overflow-x: auto;
      white-space: pre;
    }

    footer {
      text-align: center;
      margin-top: 3rem;
      padding-top: 2rem;
      border-top: 1px solid var(--border-color);
      color: var(--text-muted);
      font-size: 0.85rem;
    }

    footer a { color: var(--text-muted); text-decoration: none; }
    footer a:hover { text-decoration: underline; }
JS (js)
import "https://esm.sh/@manufosela/photo-collage";

    const collage = document.getElementById('collage');

    document.getElementById('cols').addEventListener('change', e => {
      collage.setAttribute('cols', e.target.value);
    });
    document.getElementById('rows').addEventListener('change', e => {
      collage.setAttribute('rows', e.target.value);
    });
    document.getElementById('interval').addEventListener('change', e => {
      collage.setAttribute('interval', e.target.value);
    });
    document.getElementById('randomize').addEventListener('change', e => {
      collage.toggleAttribute('randomize', e.target.checked);
    });
    document.getElementById('shuffle').addEventListener('change', e => {
      collage.toggleAttribute('shuffle', e.target.checked);
    });
    document.getElementById('polaroid').addEventListener('change', e => {
      collage.toggleAttribute('polaroid', e.target.checked);
    });
    document.getElementById('zoomable').addEventListener('change', e => {
      collage.toggleAttribute('zoomable', e.target.checked);
    });