a(name='top' id='top')

h1: a(href='//twitter.com/ycwhk' target='_top') hi

article
  h2 { #[a(href='#about') scroll to bottom] }
  p='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam metus ligula, vestibulum rhoncus elementum ut, consectetur vel justo. Nulla varius sapien odio, quis luctus magna ultricies ut. Vivamus commodo massa vitae turpis laoreet, id pretium massa ultrices. Vivamus accumsan consectetur tortor at condimentum. Etiam hendrerit eget lacus sit amet pulvinar. Praesent ac ipsum molestie, posuere purus at, hendrerit quam. In id tellus ante. Praesent semper malesuada cursus. Suspendisse potenti. Fusce viverra nulla sit amet mauris vulputate venenatis. Sed et eros eget sapien bibendum tristique. Nam eleifend ultrices tincidunt. Nam viverra dui ac turpis faucibus vestibulum.'
  
article
  h2 { add as many < article > as you like }
  p='Cras volutpat, tellus pretium commodo dignissim, elit ante pharetra risus, et tempus ligula felis ac libero. Nulla eget pulvinar arcu. Quisque nec risus ultricies dolor mattis egestas ac et nibh. Suspendisse vestibulum ligula non est porttitor consequat. Nulla at magna sed sem tincidunt efficitur a non felis. Praesent vehicula neque sed tellus consectetur, non cursus quam lobortis. Pellentesque id eleifend dui. Ut viverra, ante iaculis eleifend blandit, felis mauris fermentum nunc, in volutpat est diam vitae ipsum. Phasellus pellentesque justo quam, vitae tempor quam imperdiet eget. Donec bibendum a risus id elementum. Vestibulum aliquet porttitor eros, ac facilisis metus fermentum ac. Praesent scelerisque tincidunt hendrerit. Proin dignissim lobortis purus, sit amet vulputate ex rhoncus ac.'

article
  h2: a(name='about' id='about') About { #[a(href='#top') Back to Top] }
  ul 
    li Code by #[a(href='//twitter.com/ycwhk' target='_top') @ycwhk]
    li Photo by #[a(href='//unsplash.com/photos/DN08A8Br6rM' target='_top') Marcel Strauß]
View Compiled
@import url('https://fonts.googleapis.com/css2?family=Yomogi&family=Zen+Loop:ital@1&display=swap');

canvas {
  position: fixed;
  top: 0;
  left: 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: 100vh;
}

:root {
  scroll-behavior: smooth;
}

body {
  background: black;
  display: flex;
  flex-flow: column;
  background: #222;
}

h1 {
  font: 10vmin 'Zen Loop', cursive;
  color: white;
  text-shadow: 0 0 2px black;
  padding: 5vmin;
  box-sizing: border-box;
  width: min-content;
  background: black;
}

article {
  box-sizing: border-box;
  padding: 5vmin;
  width: 50vw;
  overflow: hidden;
  background: #111;
  color: #ddd;
  border-radius: 5px;
  border-block: 2px solid #eee;
  border-inline: 2px solid #aaa;
  box-shadow: 0 0 0 5px #000;
  font-family: 'Yomogi', cursive;
  place-self: center;
  margin-block: 10vh;

  &:last-of-type {
    border-radius: 5px 5px 0 0;
    border-block-end-width: 0;
    margin-block: 80vh;
    margin-block-end: 0;
  }
  
  h2 {
    font-weight: bold;
    margin: 1ch 0;
  }
}

a {
  color: inherit;
}
View Compiled
import * as THREE from '//cdn.skypack.dev/three@0.130.0?min'
import { gsap } from '//cdn.skypack.dev/gsap@3.7.0?min'

const imgUrl = 'https://images.unsplash.com/photo-1613721880907-77b1bb086164?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80'
const imgRatio = 634 / 951;
const targetCameraZ = 180;
const instanceSize = 1;
const randRangeZ = 2 * (targetCameraZ) * .99; // spread *.5 +-
const initCamaraZ = targetCameraZ / 5;

// ----
// main
// ----

const renderer = new THREE.WebGLRenderer({ alpha: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 2, .5, 1000);
camera.position.set(0, 0, initCamaraZ);

function f(x, y, targetZ) {
  const h = 0.5; // half h
  const d = targetCameraZ;
  const D = -targetZ + d;
  const H = h / d * D;
  const s = H / h;
  return { s, p: new THREE.Vector3(x * s, y * s, targetZ) };
}

const mesh = (() => {
  const nRow = 256;
  const nCol = nRow * imgRatio | 0;
  const sz = instanceSize;

  const geom = new THREE.BoxGeometry(sz, sz, sz).translate(0, 0, -.5 * sz);
  const mat = new THREE.MeshBasicMaterial();
  const mesh = new THREE.InstancedMesh(geom, mat, nCol * nRow);

  for (let i = 0, c = 0; i < nRow; ++i) {
    for (let j = 0; j < nCol; ++j) {
      const { p, s } = f(
        (j - nCol / 2 + .5) * sz,
        (nRow / 2 - i + .5) * sz,
        THREE.MathUtils.randFloatSpread(randRangeZ) * sz);
      const m = new THREE.Matrix4().setPosition(p)
        .multiply(new THREE.Matrix4().makeScale(s, s, s));
      mesh.setMatrixAt(c, m);
      mesh.setColorAt(c, new THREE.Color('white'));
      ++c;
    }
  }
  mesh.instanceMatrix.needsUpdate = true;
  mesh.instanceColor.needsUpdate = true;
  return mesh;
})();
scene.add(mesh);

{
  const img = new Image();
  img.onload = () => {
    const { width, height } = img;
    const can = document.createElement('canvas');
    can.height = 256;
    can.width = can.height * imgRatio | 0;
    const ctx = can.getContext('2d');
    ctx.drawImage(img, 0, 0, width, height, 0, 0, can.width, can.height,);
    const { data } = ctx.getImageData(0, 0, can.width, can.height);
    const c = new THREE.Color();
    const total = data.length >> 2;
    for (let i = 0; i < total; ++i) {
      mesh.setColorAt(i, c.setRGB(data[i * 4] / 255, data[i * 4 + 1] / 255, data[i * 4 + 2] / 255));
    }
    mesh.instanceColor.needsUpdate = true;
  }
  img.crossOrigin = '';
  img.src = imgUrl;
}

// ----
// render
// ----

renderer.setAnimationLoop(() => {
  renderer.render(scene, camera);
});

// ----
// view
// ----

function resize(w, h, dpr = devicePixelRatio) {
  renderer.setPixelRatio(dpr);
  renderer.setSize(w, h, false);
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
}
addEventListener('resize', () => resize(innerWidth, innerHeight));
dispatchEvent(new Event('resize'));
document.body.prepend(renderer.domElement);

// ----
// scroll
// ----

function setCamPos() {
  const H = document.documentElement.offsetHeight - window.innerHeight;
  const r = window.scrollY / H; // ratio
  const z = initCamaraZ + (targetCameraZ - initCamaraZ) * r;
  gsap.killTweensOf(camera.position);
  gsap.to(camera.position, { z });
}
document.addEventListener('scroll', setCamPos);
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.