Custom Code Experiment •
ThreeJS: Mosquito
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ThreeJS: Mosquito</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #000; overflow: hidden; height: 100vh; }
#bgCanvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
#animationCanvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2; cursor: grab; }
#animationCanvas.dragging { cursor: grabbing; }
#hint { position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%); z-index: 10;
color: rgba(180,140,255,0.5); font-family: monospace; font-size: 12px; letter-spacing: 0.12em;
pointer-events: none; transition: opacity 2s; user-select: none; }
</style>
</head>
<body>
<canvas id="bgCanvas"></canvas>
<canvas id="animationCanvas"></canvas>
<script>
const colors = {
body: 0x8060aa,
thorax: 0xa080cc,
wing: 0xc8b0ff,
vein: 0xf0e8ff,
leg: 0x604888,
eye: 0xff3060,
probos: 0xd0a8ff,
spot: 0x40ff90,
dark: 0x1a0a2e,
};
const bgCanvas = document.getElementById('bgCanvas');
const bgCtx = bgCanvas.getContext('2d');
const px = 7;
let cols, rows, pixels = [];
function initBackground() {
bgCanvas.width = window.innerWidth;
bgCanvas.height = window.innerHeight;
cols = Math.ceil(bgCanvas.width / px);
rows = Math.ceil(bgCanvas.height / px);
pixels = [];
for (let i = 0; i < cols * rows; i++) {
pixels.push({ b: Math.random() * 0.3, speed: 0.001 + Math.random() * 0.003 });
}
}
function animateBackground() {
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const p = pixels[y * cols + x];
p.b += p.speed;
if (p.b > 0.4) p.b = 0;
bgCtx.fillStyle = `rgb(${Math.floor(p.b*20)},${Math.floor(p.b*10)},${Math.floor(p.b*45)})`;
bgCtx.fillRect(x * px, y * px, px, px);
}
}
requestAnimationFrame(animateBackground);
}
const renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('animationCanvas'),
antialias: true, alpha: true
});
let scene, camera, mosquito;
let wingL, wingR;
let legGroups = [];
const orbit = {
theta: 0, phi: 1.1, radius: 8,
isDragging: false, lastX: 0, lastY: 0,
autoRotate: true, autoRotateSpeed: 0.003,
velTheta: 0, velPhi: 0,
};
const PHI_MIN = 0.05, PHI_MAX = Math.PI * 0.95;
const RADIUS_MIN = 3, RADIUS_MAX = 20;
function setCameraFromOrbit() {
const x = orbit.radius * Math.sin(orbit.phi) * Math.sin(orbit.theta);
const y = orbit.radius * Math.cos(orbit.phi);
const z = orbit.radius * Math.sin(orbit.phi) * Math.cos(orbit.theta);
camera.position.set(x, y, z);
camera.lookAt(0, 0, 0);
camera.updateProjectionMatrix();
}
function updateSize() {
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}
function updateCamera() {
const w = window.innerWidth;
orbit.radius = w < 480 ? 12 : w < 768 ? 10 : 8;
camera.fov = 52;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
setCameraFromOrbit();
}
function mat(color) {
return new THREE.MeshBasicMaterial({ color, wireframe: true });
}
function lineMat(color) {
return new THREE.LineBasicMaterial({ color });
}
function makeWing(side) {
const group = new THREE.Group();
const s = side;
const shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.bezierCurveTo(s*0.3, 0.18, s*1.0, 0.32, s*1.8, 0.18);
shape.bezierCurveTo(s*2.3, 0.08, s*2.4, -0.04, s*2.2, -0.10);
shape.bezierCurveTo(s*1.8, -0.22, s*0.9, -0.28, s*0.2, -0.12);
shape.bezierCurveTo(s*0.05, -0.06, 0, 0, 0, 0);
const geo = new THREE.ShapeGeometry(shape, 14);
group.add(new THREE.Mesh(geo, mat(colors.wing)));
const edgeGeo = new THREE.EdgesGeometry(geo);
group.add(new THREE.LineSegments(edgeGeo, lineMat(colors.vein)));
const veinDefs = [
{ pts: [[0,0,0.01],[s*0.8,0.18,0.01],[s*1.8,0.16,0.01],[s*2.2,0.05,0.01]], r: 0.009 },
{ pts: [[0,0,0.01],[s*0.7,0.10,0.01],[s*1.6,0.08,0.01],[s*2.1,-0.02,0.01]], r: 0.007 },
{ pts: [[s*0.1,-0.02,0.01],[s*0.8,0.02,0.01],[s*1.7,-0.01,0.01],[s*2.1,-0.08,0.01]], r: 0.006 },
{ pts: [[s*0.2,-0.06,0.01],[s*0.7,-0.06,0.01],[s*1.4,-0.09,0.01],[s*2.0,-0.14,0.01]], r: 0.005 },
];
veinDefs.forEach(v => {
const pts = v.pts.map(p => new THREE.Vector3(p[0], p[1], p[2]));
const curve = new THREE.CatmullRomCurve3(pts);
const vGeo = new THREE.TubeGeometry(curve, 8, v.r, 4, false);
group.add(new THREE.Mesh(vGeo, mat(colors.vein)));
});
for (let i = 0; i < 3; i++) {
const cx = s * (0.6 + i * 0.5);
const cvPts = [
new THREE.Vector3(cx, 0.12 - i*0.03, 0.01),
new THREE.Vector3(cx + s*0.05, -0.04 - i*0.02, 0.01),
];
const cvCurve = new THREE.CatmullRomCurve3(cvPts);
const cvGeo = new THREE.TubeGeometry(cvCurve, 4, 0.004, 4, false);
group.add(new THREE.Mesh(cvGeo, mat(colors.vein)));
}
return group;
}
function makeAntenna(side) {
const group = new THREE.Group();
const s = side;
const segments = 14;
for (let i = 0; i < segments; i++) {
const t = i / segments;
const y = 0.15 + t * 1.1;
const x = s * (0.05 + t * 0.08);
if (i < segments - 1) {
const sPts = [
new THREE.Vector3(x, y, 0),
new THREE.Vector3(x + s*0.006, y + 0.09, 0),
];
const sCurve = new THREE.CatmullRomCurve3(sPts);
group.add(new THREE.Mesh(
new THREE.TubeGeometry(sCurve, 3, 0.008 - t*0.005, 4, false),
mat(colors.probos)
));
}
if (i > 1 && i < segments - 2) {
const hairLen = 0.12 - t * 0.07;
for (let h = -1; h <= 1; h += 2) {
const hPts = [
new THREE.Vector3(x, y, 0),
new THREE.Vector3(x + s*hairLen*0.4, y + hairLen*0.3, h*hairLen*0.6),
new THREE.Vector3(x + s*hairLen*0.7, y + hairLen*0.1, h*hairLen),
];
const hCurve = new THREE.CatmullRomCurve3(hPts);
group.add(new THREE.Mesh(
new THREE.TubeGeometry(hCurve, 3, 0.003, 3, false),
mat(colors.vein)
));
}
}
}
return group;
}
function makeLeg(side, index) {
const group = new THREE.Group();
const s = side;
const zOff = (index - 1) * 0.28;
const coxPts = [
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(s*0.18, -0.08, 0.04),
];
group.add(new THREE.Mesh(
new THREE.TubeGeometry(new THREE.CatmullRomCurve3(coxPts), 4, 0.025, 5, false),
mat(colors.leg)
));
const femStart = new THREE.Vector3(s*0.18, -0.08, 0.04);
const femEnd = new THREE.Vector3(s*0.65, -0.55, 0.06);
const femPts = [femStart, new THREE.Vector3(s*0.40, -0.30, 0.05), femEnd];
group.add(new THREE.Mesh(
new THREE.TubeGeometry(new THREE.CatmullRomCurve3(femPts), 6, 0.018, 5, false),
mat(colors.body)
));
const tibEnd = new THREE.Vector3(s*1.05, -1.05, -0.05);
const tibPts = [femEnd, new THREE.Vector3(s*0.85, -0.80, 0.0), tibEnd];
group.add(new THREE.Mesh(
new THREE.TubeGeometry(new THREE.CatmullRomCurve3(tibPts), 6, 0.013, 5, false),
mat(colors.leg)
));
const tarsSegs = 3;
let tStart = tibEnd.clone();
for (let ti = 0; ti < tarsSegs; ti++) {
const tEnd = new THREE.Vector3(
tStart.x + s*0.22,
tStart.y - 0.22 + ti*0.06,
tStart.z - 0.04
);
group.add(new THREE.Mesh(
new THREE.TubeGeometry(
new THREE.CatmullRomCurve3([tStart, tEnd]), 4,
0.009 - ti*0.002, 4, false
),
mat(ti % 2 === 0 ? colors.dark : colors.vein)
));
tStart = tEnd;
}
return group;
}
function createMosquito() {
const group = new THREE.Group();
legGroups = [];
const abdSegs = 8;
for (let i = 0; i < abdSegs; i++) {
const t = i / (abdSegs - 1);
const r = 0.13 - t * 0.07;
const z = 0.1 + i * 0.30;
const isAlt = i % 2 === 0;
const geo = new THREE.CylinderGeometry(r * 0.85, r, 0.28, 12, 1);
const seg = new THREE.Mesh(geo, mat(isAlt ? colors.body : colors.dark));
seg.rotation.x = Math.PI * 0.5;
seg.position.set(0, 0, z);
group.add(seg);
const ringGeo = new THREE.TorusGeometry(r, 0.008, 6, 14);
const ring = new THREE.Mesh(ringGeo, mat(colors.vein));
ring.position.set(0, 0, z + 0.13);
group.add(ring);
}
const tailGeo = new THREE.ConeGeometry(0.04, 0.18, 8);
const tail = new THREE.Mesh(tailGeo, mat(colors.dark));
tail.rotation.x = Math.PI * 0.5;
tail.position.set(0, 0, 0.1 + abdSegs * 0.30 + 0.09);
group.add(tail);
const thoraxGeo = new THREE.SphereGeometry(0.30, 16, 14);
const thorax = new THREE.Mesh(thoraxGeo, mat(colors.thorax));
thorax.scale.set(0.9, 1.15, 1.1);
thorax.position.set(0, 0.12, -0.08);
group.add(thorax);
const scuGeo = new THREE.SphereGeometry(0.12, 10, 8);
const scu = new THREE.Mesh(scuGeo, mat(colors.body));
scu.scale.set(1, 0.6, 0.8);
scu.position.set(0, 0.22, 0.18);
group.add(scu);
const headGeo = new THREE.SphereGeometry(0.18, 14, 12);
const head = new THREE.Mesh(headGeo, mat(colors.thorax));
head.scale.set(0.95, 0.9, 1.0);
head.position.set(0, -0.04, -0.42);
group.add(head);
[-1, 1].forEach(side => {
const eyeGeo = new THREE.SphereGeometry(0.11, 12, 10);
const eye = new THREE.Mesh(eyeGeo, mat(colors.eye));
eye.scale.set(0.75, 0.85, 0.9);
eye.position.set(side * 0.15, 0.02, -0.44);
group.add(eye);
for (let fi = 0; fi < 4; fi++) {
const fGeo = new THREE.CircleGeometry(0.04, 6);
const facet = new THREE.Mesh(fGeo, mat(colors.vein));
const ang = (fi / 4) * Math.PI * 1.2 - 0.3;
facet.position.set(
side * (0.17 + Math.cos(ang)*0.05),
0.02 + Math.sin(ang)*0.05,
-0.42
);
group.add(facet);
}
});
const probPts = [
new THREE.Vector3(0, -0.08, -0.54),
new THREE.Vector3(0, -0.20, -0.90),
new THREE.Vector3(0, -0.28, -1.30),
new THREE.Vector3(0, -0.32, -1.70),
];
const probCurve = new THREE.CatmullRomCurve3(probPts);
const probGeo = new THREE.TubeGeometry(probCurve, 12, 0.012, 6, false);
group.add(new THREE.Mesh(probGeo, mat(colors.probos)));
const tipGeo = new THREE.ConeGeometry(0.008, 0.06, 5);
const tip = new THREE.Mesh(tipGeo, mat(colors.vein));
tip.rotation.x = Math.PI * 0.5;
tip.position.set(0, -0.34, -1.73);
group.add(tip);
[-1, 1].forEach(side => {
const lpPts = [
new THREE.Vector3(side*0.03, -0.06, -0.52),
new THREE.Vector3(side*0.05, -0.14, -0.72),
new THREE.Vector3(side*0.04, -0.20, -0.90),
];
group.add(new THREE.Mesh(
new THREE.TubeGeometry(new THREE.CatmullRomCurve3(lpPts), 6, 0.007, 4, false),
mat(colors.leg)
));
});
[-1, 1].forEach(side => {
const antGroup = makeAntenna(side);
antGroup.position.set(side * 0.06, 0.06, -0.52);
group.add(antGroup);
});
wingL = new THREE.Group();
wingL.position.set(0, 0.14, -0.05);
const wL = makeWing(-1);
wL.rotation.x = -0.18;
wL.rotation.z = 0.10;
wingL.add(wL);
group.add(wingL);
wingR = new THREE.Group();
wingR.position.set(0, 0.14, -0.05);
const wR = makeWing(1);
wR.rotation.x = -0.18;
wR.rotation.z = -0.10;
wingR.add(wR);
group.add(wingR);
[-1, 1].forEach(side => {
for (let li = 0; li < 3; li++) {
const legGroup = makeLeg(side, li);
legGroup.position.set(side * 0.28, -0.05, -0.15 + li * 0.28);
group.add(legGroup);
legGroups.push({ g: legGroup, side, li });
}
});
group.position.set(0, 0.3, 0.4);
return group;
}
function initOrbitControls() {
const canvas = document.getElementById('animationCanvas');
const hint = document.getElementById('hint');
function onDragStart(x, y) {
orbit.isDragging = true;
orbit.autoRotate = false;
orbit.lastX = x; orbit.lastY = y;
orbit.velTheta = 0; orbit.velPhi = 0;
canvas.classList.add('dragging');
hint.style.opacity = '0';
}
function onDragMove(x, y) {
if (!orbit.isDragging) return;
const dx = x - orbit.lastX;
const dy = y - orbit.lastY;
const speed = 0.005;
orbit.velTheta = -dx * speed;
orbit.velPhi = -dy * speed;
orbit.theta += orbit.velTheta;
orbit.phi = Math.max(PHI_MIN, Math.min(PHI_MAX, orbit.phi + orbit.velPhi));
orbit.lastX = x; orbit.lastY = y;
setCameraFromOrbit();
}
function onDragEnd() {
orbit.isDragging = false;
canvas.classList.remove('dragging');
setTimeout(() => { orbit.autoRotate = true; }, 2000);
}
canvas.addEventListener('mousedown', e => onDragStart(e.clientX, e.clientY));
window.addEventListener('mousemove', e => onDragMove(e.clientX, e.clientY));
window.addEventListener('mouseup', () => onDragEnd());
canvas.addEventListener('touchstart', e => {
e.preventDefault();
onDragStart(e.touches[0].clientX, e.touches[0].clientY);
}, { passive: false });
window.addEventListener('touchmove', e => {
e.preventDefault();
onDragMove(e.touches[0].clientX, e.touches[0].clientY);
}, { passive: false });
window.addEventListener('touchend', () => onDragEnd());
canvas.addEventListener('wheel', e => {
e.preventDefault();
orbit.radius = Math.max(RADIUS_MIN, Math.min(RADIUS_MAX, orbit.radius + e.deltaY * 0.01));
setCameraFromOrbit();
}, { passive: false });
setTimeout(() => { hint.style.opacity = '0'; }, 3000);
}
function init() {
initBackground();
animateBackground();
updateSize();
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
camera = new THREE.PerspectiveCamera(52, window.innerWidth / window.innerHeight, 0.1, 1000);
updateCamera();
mosquito = createMosquito();
scene.add(mosquito);
scene.add(new THREE.AmbientLight(0xc0a0ff, 0.5));
const pl1 = new THREE.PointLight(0xa070ff, 1.6, 100);
pl1.position.set(4, 5, 3);
scene.add(pl1);
const pl2 = new THREE.PointLight(0xff3060, 0.7, 100);
pl2.position.set(-4, -2, 2);
scene.add(pl2);
const pl3 = new THREE.PointLight(0x40d0ff, 0.5, 100);
pl3.position.set(0, 3, -8);
scene.add(pl3);
animate();
initOrbitControls();
}
function animate() {
requestAnimationFrame(animate);
const t = Date.now() * 0.001;
if (!orbit.isDragging) {
if (orbit.autoRotate) {
orbit.theta += orbit.autoRotateSpeed;
} else {
orbit.velTheta *= 0.92;
orbit.velPhi *= 0.92;
orbit.theta += orbit.velTheta;
orbit.phi = Math.max(PHI_MIN, Math.min(PHI_MAX, orbit.phi + orbit.velPhi));
}
setCameraFromOrbit();
}
if (mosquito) {
mosquito.position.y = 0.3 + Math.sin(t * 1.8) * 0.06;
mosquito.rotation.x = Math.sin(t * 2.3) * 0.03;
mosquito.rotation.z = Math.sin(t * 1.5) * 0.02;
if (wingL) {
wingL.rotation.z = Math.sin(t * 18.0) * 0.55;
wingL.rotation.x = Math.sin(t * 17.0 + 0.4) * 0.12;
wingL.rotation.y = Math.sin(t * 19.0 + 0.8) * 0.08;
}
if (wingR) {
wingR.rotation.z = -Math.sin(t * 18.5 + 0.2) * 0.55;
wingR.rotation.x = Math.sin(t * 17.5 + 1.1) * 0.12;
wingR.rotation.y = -Math.sin(t * 19.5 + 0.5) * 0.08;
}
legGroups.forEach(({ g, side, li }) => {
const phase = li * 0.7 + (side > 0 ? 1.2 : 0);
g.rotation.z = side * (Math.sin(t * 2.2 + phase) * 0.04);
g.rotation.x = Math.sin(t * 1.8 + phase * 0.5) * 0.03;
});
}
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
updateSize();
initBackground();
if (camera) updateCamera();
});
window.addEventListener('load', init);
</script>
</body>
</html>