Skip to content

Instantly share code, notes, and snippets.

@drawby
Created February 24, 2026 16:53
Show Gist options
  • Select an option

  • Save drawby/b54d874182cf05748eebe97e030ebc85 to your computer and use it in GitHub Desktop.

Select an option

Save drawby/b54d874182cf05748eebe97e030ebc85 to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>☃️ Snowman Builder</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>☃️ Build Your Snowman!</h1>
<div id="container">
<div id="ui">
<h2>Head</h2>
<button onclick="loadPart('head','head1.glb')">Happy</button>
<button onclick="loadPart('head','head2.glb')">Cool</button>
<h2>Torso</h2>
<button onclick="loadPart('torso','torso1.glb')">Round</button>
<button onclick="loadPart('torso','torso2.glb')">Fat</button>
<h2>Bottom</h2>
<button onclick="loadPart('bottom','bottom1.glb')">Big</button>
<button onclick="loadPart('bottom','bottom2.glb')">Huge</button>
<h2>Resize Head</h2>
<input type="range" min="0.5" max="2"
step="0.1"
value="1"
oninput="resizePart('head', this.value)">
<button onclick="exportSTL()">Export STL</button>
</div>
<canvas id="canvas"></canvas>
</div>
<script type="module" src="app.js"></script>
</body>
</html>
import * as THREE from 'https://unpkg.com/three@0.160.0/build/three.module.js';
import { OrbitControls }
from 'https://unpkg.com/three@0.160.0/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader }
from 'https://unpkg.com/three@0.160.0/examples/jsm/loaders/GLTFLoader.js';
import { STLExporter }
from 'https://unpkg.com/three@0.160.0/examples/jsm/exporters/STLExporter.js';
let scene, camera, renderer, controls;
let snowman;
let parts = {
head:null,
torso:null,
bottom:null
};
const loader = new GLTFLoader();
init();
animate();
function init(){
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
camera = new THREE.PerspectiveCamera(
75,
(window.innerWidth-300)/window.innerHeight,
0.1,
1000
);
camera.position.set(0,3,6);
renderer = new THREE.WebGLRenderer({
canvas:document.getElementById("canvas"),
antialias:true
});
renderer.setSize(window.innerWidth-300,window.innerHeight);
controls = new OrbitControls(camera,renderer.domElement);
const light = new THREE.DirectionalLight(0xffffff,1);
light.position.set(5,10,5);
scene.add(light);
const ambient = new THREE.AmbientLight(0xffffff,0.6);
scene.add(ambient);
snowman = new THREE.Group();
scene.add(snowman);
}
window.loadPart = function(type,file){
const path = `models/${type}s/${file}`;
loader.load(path,function(gltf){
const model = gltf.scene;
model.traverse(child=>{
if(child.isMesh){
child.castShadow=true;
child.receiveShadow=true;
}
});
if(parts[type])
snowman.remove(parts[type]);
parts[type]=model;
snowman.add(model);
snapParts();
});
};
function snapParts(){
if(parts.bottom){
const bottomBox =
new THREE.Box3().setFromObject(parts.bottom);
const bottomHeight =
bottomBox.max.y-bottomBox.min.y;
parts.bottom.position.y = 0;
if(parts.torso){
const torsoBox =
new THREE.Box3().setFromObject(parts.torso);
const torsoHeight =
torsoBox.max.y-torsoBox.min.y;
parts.torso.position.y =
bottomHeight;
if(parts.head){
const headBox =
new THREE.Box3().setFromObject(parts.head);
const headHeight =
headBox.max.y-headBox.min.y;
parts.head.position.y =
bottomHeight + torsoHeight;
}
}
}
}
window.resizePart=function(type,scale){
if(parts[type]){
parts[type].scale.set(scale,scale,scale);
snapParts();
}
};
window.exportSTL=function(){
const exporter=new STLExporter();
const clone=snowman.clone(true);
clone.updateMatrixWorld(true);
const result=exporter.parse(clone);
const blob=new Blob([result],{type:'text/plain'});
const link=document.createElement("a");
link.href=URL.createObjectURL(blob);
link.download="snowman.stl";
link.click();
};
function animate(){
requestAnimationFrame(animate);
snowman.rotation.y+=0.005;
renderer.render(scene,camera);
}
body{
margin:0;
font-family: Comic Sans MS, Arial;
background:#87CEEB;
text-align:center;
}
h1{
font-size:40px;
}
#container{
display:flex;
}
#ui{
width:300px;
background:white;
padding:20px;
}
button{
font-size:20px;
padding:10px;
margin:5px;
width:100%;
border-radius:10px;
border:none;
background:#ffcc00;
cursor:pointer;
}
button:hover{
background:#ffaa00;
}
input[type="range"]{
width:100%;
}
canvas{
flex-grow:1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment