Przejdź do treści
<code>pen.pl

Kostka do gry 3D

Inne

Podgląd

Ładowanie podglądu…

Kostka 3D obraca się po rzucie i pokazuje wylosowaną liczbę oczek.

0,0 / 5 Brak ocen

Kod źródłowy

Licencja MIT — także do użytku komercyjnego. Zachowaj informacje o autorze i licencji.

HTML
<main class="pen" data-motion="paused"><header><div><h1>Kostka do gry 3D</h1><p>Rzuć kostką.</p></div></header><section class="scene" aria-label="Wizualizacja"><div class="dice" aria-hidden="true"></div><div class="dice-mark" aria-hidden="true">1 / 6</div></section><div class="toolbar"><button id="motion" type="button" aria-pressed="false">Uruchom</button><button id="roll" type="button">Rzuć kostką</button><output id="dice-value" aria-live="polite">Wynik: 1</output></div></main>
CSS

* { box-sizing: border-box; }
html, body { margin: 0; min-width: 280px; }
body { background: var(--paper); color: var(--ink); font: 16px/1.5 Arial, sans-serif; }
.pen { min-height: 100vh; padding: clamp(16px, 3vw, 30px); display: flex; flex-direction: column; gap: 16px; max-width: 1120px; margin: auto; }
header { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
h1 { font-size: clamp(26px, 4.1vw, 42px); line-height: 1.08; margin: 0 0 8px; letter-spacing: -.04em; }
p { margin: 0; max-width: 64ch; }
.scene { position: relative; min-height: 230px; flex: 1; display: grid; place-items: center; overflow: hidden; }
.scene canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 20px; }
.toolbar label { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; min-height: 44px; }
button, select { font: inherit; color: var(--ink); background: var(--paper); border: 1px solid currentColor; border-radius: 0; min-height: 44px; padding: 8px 15px; cursor: pointer; }
button:hover { background: var(--ink); color: var(--paper); }
button:focus-visible, select:focus-visible, input:focus-visible { outline: 3px solid var(--accent); outline-offset: 4px; }
input[type=range] { width: 145px; max-width: 42vw; accent-color: var(--accent); min-height: 36px; cursor: pointer; }
output, .hint { font-size: 14px; }
[data-motion="paused"] .animated { animation-play-state: paused !important; }
@media (max-width: 500px) { header { gap: 6px; } .scene { min-height: 280px; } .toolbar { gap: 10px; } .toolbar label { font-size: 14px; } }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: 0s !important; scroll-behavior: auto !important; } }
body{--paper:#a32638;--ink:#fff5df;--accent:#ffd271}.scene{perspective:650px;background:#861c2e}.dice{width:130px;height:130px;position:relative;transform-style:preserve-3d;transform:rotateX(-20deg) rotateY(-25deg)}.dice.rolling{transition:transform .7s cubic-bezier(.22,.68,.12,1)}.dice-face{position:absolute;inset:0;background:#fff5df;border:2px solid #511524;display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);padding:18px;gap:7px;backface-visibility:hidden}.pip{width:19px;height:19px;place-self:center;border-radius:50%;background:#70172a}.dice-mark{position:absolute;right:20px;bottom:15px;font:14px monospace}
JavaScript
(() => {

const root = document.querySelector('.pen');
const preference = matchMedia('(prefers-reduced-motion: reduce)');
const motionButton = document.querySelector('#motion');
let playing = !preference.matches, elapsed = 0, paint = null;
function syncMotion() {
  root.dataset.motion = playing ? 'running' : 'paused';
  motionButton.textContent = playing ? 'Wstrzymaj' : 'Uruchom';
  motionButton.setAttribute('aria-pressed', String(playing));
}
motionButton.addEventListener('click', () => { playing = !playing; syncMotion(); });
preference.addEventListener('change', event => { playing = !event.matches; syncMotion(); });
syncMotion();
function run(draw) {
  paint = draw;
  draw(0);
  let last = performance.now(), lastPaint = 0;
  function frame(now) {
    if (playing && !document.hidden) { elapsed += Math.min(now - last, 48) / 1000; if (now - lastPaint >= 30) { draw(elapsed); lastPaint = now; } }
    last = now;
    requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
}
function canvasSurface(selector) {
  const canvas = document.querySelector(selector), context = canvas.getContext('2d');
  if (!context) { canvas.replaceWith(document.createTextNode('Ta wizualizacja wymaga obsługi Canvas 2D.')); return null; }
  const surface = { canvas, context, width: 0, height: 0 };
  function resize() {
    const box = canvas.parentElement.getBoundingClientRect(), ratio = Math.min(devicePixelRatio || 1, 2);
    surface.width = box.width; surface.height = box.height;
    canvas.width = Math.max(1, Math.round(box.width * ratio)); canvas.height = Math.max(1, Math.round(box.height * ratio));
    context.setTransform(ratio, 0, 0, ratio, 0, 0);
    if (paint) paint(elapsed);
  }
  resize(); new ResizeObserver(resize).observe(canvas.parentElement);
  return surface;
}
const dice=document.querySelector('.dice'),positions=[[4],[0,8],[0,4,8],[0,2,6,8],[0,2,4,6,8],[0,2,3,5,6,8]],transforms=['translateZ(65px)','rotateY(90deg) translateZ(65px)','rotateY(180deg) translateZ(65px)','rotateY(-90deg) translateZ(65px)','rotateX(90deg) translateZ(65px)','rotateX(-90deg) translateZ(65px)'];positions.forEach((places,index)=>{const face=document.createElement('div');face.className='dice-face';face.style.transform=transforms[index];places.forEach(position=>{const pip=document.createElement('i');pip.className='pip';pip.style.gridRow=String(Math.floor(position/3)+1);pip.style.gridColumn=String(position%3+1);face.append(pip);});dice.append(face);});const rotations=[[0,0],[0,-90],[0,-180],[0,90],[-90,0],[90,0]];let rolled=false,rollTimer=0;document.querySelector('#roll').addEventListener('click',()=>{const value=1+Math.floor(Math.random()*6);clearTimeout(rollTimer);dice.classList.add('rolling');rolled=true;playing=false;syncMotion();dice.style.transform=`rotateX(${rotations[value-1][0]}deg) rotateY(${rotations[value-1][1]}deg)`;document.querySelector('#dice-value').value='Wynik: '+value;document.querySelector('.dice-mark').textContent=value+' / 6';rollTimer=setTimeout(()=>dice.classList.remove('rolling'),750);});motionButton.addEventListener('click',()=>{clearTimeout(rollTimer);dice.classList.remove('rolling');if(playing)rolled=false;});run(time=>{if(!rolled)dice.style.transform=`rotateX(-20deg) rotateY(${-25+time*12}deg)`;});
})();

Edytuj swój pen

Podaj adres e-mail użyty przy zapisie. Wyślemy link do edycji.

Link jest ważny przez 30 minut.