A quick play with some 3D CSS and animation.
Will add some more animation to this when I have more time
A Pen by Peter Norton on CodePen.
// Lil minecraft wolf | |
// source: | |
// https://minecraft.fandom.com/wiki/Wolf | |
// Inspired by the works of Grant Jenkins: | |
// https://codepen.io/grantjenkins/pen/ZExEOex?editors=0100 | |
mixin box(name) | |
.box(class=name) | |
.lt | |
.rt | |
.tp | |
.bt | |
.ft | |
.bk | |
.scene | |
+box('mud') | |
+box('grass') | |
.shadow | |
.wolf | |
.legs | |
+box('leg lt bk') | |
+box('leg lt ft') | |
+box('leg rt bk') | |
+box('leg rt ft') | |
.tails | |
+box('tail') | |
.bodys | |
+box('body ft') | |
+box('body bk') | |
.heads | |
+box('head') | |
+box('snout') | |
.mouths | |
+box('mouth') | |
+box('ear lt') | |
+box('ear rt') | |
A quick play with some 3D CSS and animation.
Will add some more animation to this when I have more time
A Pen by Peter Norton on CodePen.
// Enable scene interaction | |
const scene = document.querySelector('.scene'); | |
const rotation = []; | |
const startPos = []; | |
let scale = 1; | |
const start = event => { | |
// get the rx and ry of scene | |
let style = window.getComputedStyle(scene); | |
rotation[0] = parseInt(style.getPropertyValue('--rx')); | |
rotation[1] = parseInt(style.getPropertyValue('--ry')); | |
// mouse starting position | |
startPos[0] = event.clientX || event.touches[0].clientX; | |
startPos[1] = event.clientY || event.touches[0].clientY; | |
}; | |
const update = event => { | |
if (startPos.length > 0) { | |
// track changes to the x and y | |
let x = (event.clientX || event.touches[0].clientX) - startPos[0]; | |
let y = (event.clientY || event.touches[0].clientY) - startPos[1]; | |
// update the scene | |
scene.style.setProperty('--rx', rotation[0] - y + 'deg'); | |
scene.style.setProperty('--ry', rotation[1] + x + 'deg'); | |
// log output | |
console.table({ | |
rx: `${rotation[0] - y}deg`, | |
ry: `${rotation[1] - x}deg` | |
}); | |
} | |
}; | |
const end = event => { | |
startPos.length = 0; | |
}; | |
scene.addEventListener('touchstart', start, false); | |
scene.addEventListener('mousedown', start, false); | |
document.addEventListener('touchmove', update, false); | |
document.addEventListener('mousemove', update, false); | |
document.addEventListener('touchend', end, false); | |
document.addEventListener('mouseup', end, false); | |
// zoom in out on mouse wheel | |
scene.addEventListener('wheel', event => { | |
event.preventDefault(); | |
scale += event.deltaY * -0.01; | |
scale = scale > 3 ? 3 : scale < 0.25 ? 0.25 : scale; | |
scene.style.setProperty('--scene-scale', scale); | |
}, { passive: false }); |
// Cube | |
// Sizing: --w: width, --h: height, --d: depth | |
// Position: --x: x-axis translation, --y: y-axis translation, --z: z-axis translation | |
.box { | |
--x: 0; // default | |
--y: 0; // default | |
--z: 0; // default | |
--d2: calc(var(--d) / 2); | |
position: absolute; | |
width: var(--w); | |
height: var(--h); | |
transform: translate3d(var(--x), var(--y), var(--z)); | |
div { | |
position: absolute; | |
// outline: 1px solid rgba(grey, .25); | |
} | |
.lt { | |
width: var(--d); | |
height: var(--h); | |
left: 0; | |
transform: rotateY(-90deg) translatez(var(--d2)); | |
} | |
.rt { | |
width: var(--d); | |
height: var(--h); | |
right: 0; | |
transform: rotateY(90deg) translatez(var(--d2)); | |
} | |
.tp { | |
width: var(--w); | |
height: var(--d); | |
top: 0; | |
transform: rotateX(90deg) translateZ(var(--d2)); | |
} | |
.bt { | |
width: var(--w); | |
height: var(--d); | |
bottom: 0; | |
transform: rotateX(-90deg) translateZ(var(--d2)); | |
} | |
.ft { | |
width: var(--w); | |
height: var(--h); | |
transform: translateZ(var(--d2)); | |
} | |
.bk { | |
width: var(--w); | |
height: var(--h); | |
transform: rotateY(180deg) translateZ(var(--d2)); | |
} | |
} | |
@mixin color($col) { | |
// lit from front-left | |
.ft { background: $col } | |
.tp { background: lighten($col, 8%) } | |
.lt { background: lighten($col, 4%) } | |
.bk { background: darken($col, 8%) } | |
.rt { background: darken($col, 4%) } | |
.bt { background: darken($col, 20%) } | |
} | |
@mixin vars($w: 0, $h: 0, $d: 0, $x: 0, $y: 0, $z: 0) { | |
--w: calc(#{$w} * 1vmin); // width | |
--h: calc(#{$h} * 1vmin); // height | |
--d: calc(#{$d} * 1vmin); // depth | |
--x: calc(#{$x} * 1vmin); // translate-x | |
--y: calc(#{$y} * 1vmin); // translate-y | |
--z: calc(#{$z} * 1vmin); // translate-z | |
} | |
// Setting up the scene | |
:root { | |
--scene-width: 60vmin; | |
--scene-height: 20vmin; | |
--scene-depth: 60vmin; | |
--scene-scale: 1; | |
} | |
body { | |
display: grid; | |
place-items: center; | |
min-height: 100vh; | |
overflow: hidden; | |
perspective: 1000px; | |
background-image: radial-gradient(#c9d8f7, #3874f5); | |
} | |
.scene { | |
--rx: -15deg; | |
--ry: -55deg; | |
position: relative; | |
transform-style: preserve-3d; | |
width: var(--scene-width); | |
height: var(--scene-height); | |
cursor: grab; | |
transform: | |
rotateX(var(--rx)) | |
rotateY(var(--ry)) | |
scale3d(var(--scene-scale), var(--scene-scale), var(--scene-scale)); | |
* { | |
box-sizing: border-box; | |
transform-style: preserve-3d; | |
} | |
&:active { | |
cursor: grabbing; | |
} | |
} | |
// Ground | |
.grass { | |
--w: var(--scene-width); | |
--d: var(--scene-depth); | |
--h: 1vmin; | |
--y: var(--scene-height); | |
@include color(#659a42); | |
} | |
.mud { | |
--w: var(--scene-width); | |
--d: var(--scene-depth); | |
--h: 4vmin; | |
--y: calc(var(--scene-height) + 1vmin); | |
@include color(#6a5040); | |
} | |
.shadow { | |
width: 12vmin; | |
height: 25vmin; | |
position: absolute; | |
background: rgba(black, .1); | |
left: 24vmin; | |
transform: rotatex(90deg) translatez(-7.5vmin); | |
filter: blur(4px); | |
} | |
// Wolf | |
.wolf { | |
transform: translateX(25vmin) translateY(2vmin); | |
@include color(#999797); | |
.tail { | |
// vars= (w, h, d, x, y, x) | |
@include vars(3, 3, 10, 3.5, 2, -12); | |
} | |
.leg { | |
@include vars(3, 8, 3, 1, 10, 7); | |
&.rt { | |
--x: 6vmin; | |
} | |
&.bk { | |
--z: -7vmin; | |
} | |
} | |
.body { | |
&.ft { | |
@include vars(11, 10, 8, -0.5, 0, 6); | |
@include color(#8f8689); | |
.ft { | |
background: #a71c14; | |
} | |
} | |
&.bk { | |
@include vars(8, 8, 12, 1, 2, -4); | |
} | |
} | |
.head { | |
@include vars(9, 8, 4, 0.5, 1, 12); | |
.ft::before, .ft::after { | |
content: ''; | |
position: absolute; | |
background: black; | |
width: 1.5vmin; | |
height: 2vmin; | |
left: 2vmin; | |
top: 2vmin; | |
} | |
.ft::after { | |
left: 5.5vmin; | |
} | |
} | |
.snout { | |
@include color(#b8aaa8); | |
@include vars(4, 2.5, 4, 3, 5, 16); | |
.ft::before, | |
.tp::before { | |
content: ''; | |
position: absolute; | |
background: #32312d; | |
width: 2vmin; | |
height: 1vmin; | |
left: 1vmin; | |
top: 0; | |
} | |
.tp:before { | |
top: unset; | |
bottom: 0; | |
height: 1.5vmin; | |
} | |
} | |
.mouth { | |
@include color(#32312d); | |
@include vars(4, 1.5, 4, 3, 7.5, 16); | |
} | |
.ear { | |
@include vars(3, 3, 1, 0.5, -2, 11); | |
&.rt { | |
--x: 6.5vmin; | |
} | |
> div:not(.ft) { | |
background: #32312d; | |
} | |
} | |
} | |
// Animation | |
.tails { | |
transform-origin: 3.5vmin 2vmin -9vmin; | |
animation: wag .5s ease-in-out infinite alternate; | |
} | |
.mouths { | |
transform-origin: 3vmin 7.5vmin 14vmin; | |
animation: bark 4s ease-in-out infinite alternate; | |
} | |
@keyframes wag { | |
from { | |
transform: rotateX(-20deg) rotateY(-25deg); | |
} | |
to { | |
transform: rotateX(-20deg) rotateY(25deg); | |
} | |
} | |
@keyframes bark { | |
0%, 20% { | |
transform: rotateX(0deg); | |
} | |
10% { | |
transform: rotateX(-20deg); | |
} | |
} |