JSFiddle - React, Tailwind, and code Playground

by Hugo Vale Pereira

HTML

<div id="status">preloading...</div>
<button id="btn" disabled onclick="showAll()">Show all</button>
<div class="grid" id="grid"></div>

CSS

body { font-family: monospace; padding: 20px; background: #111; color: #ccc; }
  #status { margin-bottom: 10px; color: yellow; }
  button { padding: 10px 20px; font-size: 14px; cursor: pointer; margin-bottom: 20px; }
  button:disabled { opacity: 0.4; cursor: not-allowed; }
  .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
  .cell { background: #222; padding: 10px; }
  .cell h3 { font-size: 11px; margin-bottom: 4px; color: #fff; }
  .cell p { font-size: 10px; color: #666; margin-bottom: 6px; }
  .cell .time { font-size: 10px; color: #4caf50; min-height: 14px; }
  .wrap { height: 140px; background: #333; overflow: hidden; }
  .wrap img, .wrap canvas, .wrap svg { width: 100%; height: 100%; object-fit: cover; display: block; }
  .bg { width: 100%; height: 100%; background-size: cover; background-position: center; }

JavaScript

const IMG_URL = 'https://picsum.photos/5000/5000'
let blobUrl = null
let bitmap = null

// --- two real-world preload approaches, race them ---

// approach A: fetch() — modern, promise-based
const viaFetch = fetch(IMG_URL)
  .then(r => r.blob())
  .then(async blob => {
    blobUrl = URL.createObjectURL(blob)
    bitmap = await createImageBitmap(blob)
    console.log('preloaded via fetch()')
  })

// approach B: new Image() + canvas — classic, wider compat
const viaImage = new Promise((resolve, reject) => {
  const img = new Image()
  img.crossOrigin = 'anonymous'
  img.onload = () => {
    const c = document.createElement('canvas')
    c.width = img.naturalWidth
    c.height = img.naturalHeight
    c.getContext('2d').drawImage(img, 0, 0)
    c.toBlob(async blob => {
      // only use if fetch didn't already get it
      if (!blobUrl) {
        blobUrl = URL.createObjectURL(blob)
        bitmap = await createImageBitmap(blob)
        console.log('preloaded via new Image()')
      }
      resolve()
    })
  }
  img.onerror = reject
  img.src = IMG_URL
})

// whoever wins, enable the button
Promise.race([viaFetch, viaImage])
  .then(() => {
    document.getElementById('status').textContent = 'ready'
    document.getElementById('btn').disabled = false
  })
  .catch(() => {
    document.getElementById('status').textContent = 'preload failed'
  })

// --- display approaches ---
const approaches = [
  {
    name: '<img> src direct',
    desc: 'sets src on click — downloads fresh',
    init: wrap => { const el = document.createElement('img'); wrap.appendChild(el); return el },
    show: el => { el.src = IMG_URL + '?r=' + Date.now(); return el }
  },
  {
    name: '<img> src blob',
    desc: 'src = blobUrl — from memory',
    init: wrap => { const el = document.createElement('img'); wrap.appendChild(el); return el },
    show: el => { el.src = blobUrl; return el }
  },
  {
    name: 'createElement +...