• Introduction
This mini-framework is made for creating and animating CSS3D scenes very easily.
The black square on the left is the viewport. Everything is displayed inside it (you can make it fullscreen if you want).
The red dot is the virtual camera. All the scene will move relatively to to this dot to simulate camera movement.
The camera and the scene's items have a default 0.5s transition when moving / rotating, but it's editable.
• API
All the parameters are optional.
Legend: n: name, g: group, w/h/d: width/height/depth, b/b2/b3: background color(s), x/y/z: position, rx/ry/rz: rotation, html: innerHTML, css: CSS class, o:origin.
C.set_unit(u) // set size unit (default "px". Use "vmin" for responsiveness)
C.set_perspective(p) // set scene's perspective (500px by default)
C.group({n,g,w,h,x,y,z,rx,ry,rz}) // create a group
C.plane({n,g,w,h,b,x,y,z,rx,ry,rz,html,css}) // create a plane
C.sprite({n,w,h,b,x,y,z,rx,ry,rz,html,css}) // create a sprite (a plane always facing the camera)
C.cube({n,g,w,h,d,b,b2,b3,x,y,z,rx,ry,rz,css}) // create a cube
C.pyramid({n,g,w,h,d,b,b2,b3,x,y,z,rx,ry,rz,css}) // create a square-based pyramid
C.camera({x,y,z,rx,ry,rz}) // move and/or rotate the camera
C.move({n,x,y,z,rx,ry,rz}) // move and/or rotate a scene item or a group
• CSS shapes
Planes and sprites are rectangle by default but can use these built-in CSS classes:
.circle // ⬤
.triangle // ▲
.triangle-bottom // ▼
.triangle-left // ◄
.triangle-right // ►
.triangle-top-left // ◤
.triangle-top-right // ◥
.triangle-bottom-left // ◣
.triangle-bottom-right // ◢
• Demo
Here's a list of things you can do with this framework (click each button to test each snippet):
1) Add a green plane called "ground" in the scene:
2) Move the camera higher:
3) Rotate the camera:
4) Change the perspective value:
By default, all elements have their transformation origin at the center. To make an element stand vertically, it's simpler to set it at the bottom:
5) Add a vertical plane called "p1" standing on its bottom side:
6) Add a vertical triangle plane called "t1":
7) Add a sprite called "s1":
8) Add a circle sprite called "c1":
9) Rotate the camera again to see the sprites facing it
10) Add a group calleg "g1":
11) Add a cube called "c1": (colors b2 and b3 can be used to simulate shading)
12) Add a pyramid called "py1" in the group:
13) Move shapes around
14) Move a group
15) Remove items
16) Animate the camera
• Other demos and tricks
- King Bob-Omb demake (showing how a circle sprite can be used as a sphere, like in Mario 64)
- First-person view (Do a translateZ on the scene equal to the -1 * the perspective value and you'll see the scene in FPV)
• Download (zip, 1.53kb)
• Source code
HTML:
<div id=viewport>
<div id=camera>
<div id=scene></div>
</div>
</div>
CSS:
#viewport{width:500px;height:500px;overflow:hidden;border:1px solid;position:relative;perspective:500px}
#viewport *{transform-style:preserve-3d;box-sizing:border-box}
#camera{width:0;height:0;position:absolute;top:50%;left:50%}
/* Transitions (editable) */
#scene,.plane,.sprite,.group{transition:.5s linear}
/* shapes (optional) */
.triangle{clip-path:polygon(50% 0,100% 100%,0 100%);border:none}
.triangle-bottom{clip-path:polygon(0 0,100% 0,50% 100%)}
.triangle-left{clip-path:polygon(0 50%,100% 0,100% 100%)}
.triangle-right{clip-path:polygon(0 0,100% 50%,0 100%)}
.triangle-top-left{clip-path:polygon(0 0,100% 0,0 100%)}
.triangle-top-right{clip-path:polygon(0 0,100% 0,100% 100%)}
.triangle-bottom-left{clip-path:polygon(0 0,100% 100%,0 100%)}
.triangle-bottom-right{clip-path:polygon(100% 0,100% 100%,0 100%)}
.circle{border-radius:50%}
JS:
C = {
unit: "px",
camX: 0,
camY: 0,
camZ: 0,
camRX: 0,
camRY: 0,
camRZ: 0,
sprite_count: 0,
sprites: [],
plane_count: 0,
cube_count: 0,
pyramid_count: 0,
options: {},
$: t => self[t],
set_unit: t => C.unit=t,
set_perspective: t => viewport.style.perspective=`${t}${C.unit}`,
// Initialize an object's properties
init: t => {
t.css||(t.css=""),
t.html||(t.html=""),
t.g||(t.g="scene"),
t.o||(t.o="center"),
t.o=="top left"&&(t.x+=t.w/2,t.y+=t.h/2),
t.o=="top"&&(t.y+=t.h/2),
t.o=="top right"&&(t.x-=t.w/2,t.y+=t.h/2),
t.o=="right"&&(t.x-=t.w/2),
t.o=="bottom right"&&(t.x-=t.w/2,t.y-=t.h/2),
t.o=="bottom"&&(t.y-=t.h/2),
t.o=="bottom left"&&(t.x+=t.w/2,t.y-=t.h/2),
t.o=="left"&&(t.x+=t.w/2),
t.w||(t.w=0),
t.h||(t.h=0),
t.x||(t.x=0),
t.y||(t.y=0),
t.z||(t.z=0),
t.rx||(t.rx=0),
t.ry||(t.ry=0),
t.rz||(t.rz=0),
t.sx||(t.sx=1),
t.sy||(t.sy=1),
t.sz||(t.sz=1),
C.options[t.n]=t
},
// Group of objects
group: t => {
t.d||t.d===0||(t.d=t.h),
C.init(t),
C.$(t.g).innerHTML+=`<div id="${t.n}"class="group ${t.css}"style="position:absolute;width:${t.w}${C.unit};height:${t.d}${C.unit};transform:${C.tr(t)}">`
},
// Plane
plane: t => {
t.n||(t.n=`plane${C.plane_count++}`),
C.init(t),
C.$(t.g).innerHTML+=`<div id="${t.n}"class="plane ${t.css}"style="position:absolute;width:${t.w}${C.unit};height:${t.h}${C.unit};background:${t.b};transform-origin:${t.o};transform:${C.tr(t)}">${t.html}`,
C.camera()
},
// Sprite (optional)
sprite: t => {
t.n||(t.n=`sprite${C.sprite_count++}`),
C.init(t),
C.$(t.g).innerHTML+=`<div id="${t.n}"class="sprite ${t.css}"style="position:absolute;width:${t.w}${C.unit};height:${t.h}${C.unit};background:${t.b};transform-origin:${t.o};transform:${C.tr(t)}">${t.html}`,
C.sprites.push(t.n),
C.camera()
},
// Cube (optional)
cube: t => {
t.n||(t.n=`cube${C.cube_count++}`),
C.init(t),
C.group(t),
C.plane({g:t.n,x:t.w/2,y:t.d/2,w:t.w,h:t.d,b:t.b,css:"bottom"}),
C.plane({g:t.n,y:t.d/2,w:t.d,h:t.h,b:t.b1||t.b,rx:-90,ry:-90,o:"bottom",css:"left"}),
C.plane({g:t.n,x:t.w,y:t.d/2,w:t.d,h:t.h,b:t.b2||t.b,rx:-90,ry:-90,o:"bottom",css:"right"}),
C.plane({g:t.n,x:t.w/2,y:t.d,w:t.w,h:t.h,b:t.b1||t.b,rx:-90,o:"bottom",css:"front"}),
C.plane({g:t.n,x:t.w/2,y:0,w:t.w,h:t.h,b:t.b2||t.b,rx:-90,o:"bottom",css:"back"}),
C.plane({g:t.n,x:t.w/2,y:t.d/2,z:t.h,w:t.w,h:t.d,b:t.b,css:"top"})
},
// Pyramid (optional)
pyramid: t => {
t.n||(t.n=`pyramid${C.pyramid_count++}`),
C.init(t),
C.group({n:t.n,g:t.g,x:t.x,y:t.y,z:t.z,w:100,d:100,rx:t.rx,ry:t.ry,rz:t.rz,sx:t.w/100,sy:t.d/100,sz:t.h/86.6025,css:t.css}),
C.plane({g:t.n,x:50,y:50,w:100,h:100,b:t.b,css:"bottom"}),
C.plane({g:t.n,y:50,w:100,h:100,b:t.b,ry:-60,rz:90,css:"triangle left",o:"bottom"}),
C.plane({g:t.n,x:100,y:50,w:100,h:100,b:t.b2||t.b,ry:-120,rz:90,css:"triangle right",o:"bottom"}),
C.plane({g:t.n,x:50,y:0,w:100,h:100,b:t.b,rx:-120,css:"triangle back",o:"bottom"}),
C.plane({g:t.n,x:50,y:100,w:100,h:100,b:t.b1||t.b,rx:-60,css:"triangle front",o:"bottom"})
},
// Move the camera
camera: t => {
t&&(t.x||0===t.x)&&(C.camX=t.x),
t&&(t.y||0===t.y)&&(C.camY=t.y),
t&&(t.z||0===t.z)&&(C.camZ=t.z),
t&&(t.rx||0===t.rx)&&(C.camRX=t.rx),
t&&(t.ry||0===t.ry)&&(C.camRY=t.ry),
t&&(t.rz||0===t.rz)&&(C.camRZ=t.rz),
C.camX+=(Math.random()-.5)/1e3,
scene.style.transform=`translateX(${-C.camX}${C.unit})translateY(${-C.camY}${C.unit})translateZ(${-C.camZ}${C.unit})rotateX(${C.camRX}deg)rotateY(${C.camRY}deg)rotateZ(${C.camRZ}deg)`;
for(var r in C.sprites){
var n=C.$(C.sprites[r]),
o=n.style.transform.replace(/ *rotate.*\(.*?deg\)/g,"");
n.style.transform=o+`rotateZ(${-C.camRZ}deg)rotateX(${-C.camRX}deg)`
}
},
// Move an object
move: t => {
if(t.n){
var r=C.$(t.n),
n=C.options[t.n];
(t.x||0===t.x)&&(n.x=t.x),
(t.y||0===t.y)&&(n.y=t.y),
(t.z||0===t.z)&&(n.z=t.z),
(t.rx||0===t.rx)&&(n.rx=t.rx),
(t.ry||0===t.ry)&&(n.ry=t.ry),
(t.rz||0===t.rz)&&(n.rz=t.rz),
C.options[t.n]=n,
r.style.transform=C.tr(n)
}
},
// CSS3D transform string
tr: t => `translateX(-50%)translateY(-50%)translateX(${t.x}${C.unit})translateY(${t.y}${C.unit})translateZ(${t.z}${C.unit})rotateX(${t.rx}deg)rotateY(${t.ry}deg)rotateZ(${t.rz}deg)scaleX(${t.sx})scaleY(${t.sy})scaleZ(${t.sz})`
}