mixin cylinder(radius = 10, sides = 10, cut = [5, 10], top = true, bottom = true)
- const innerAngle = (((sides - 2) * 180) / sides) * 0.5
- const cosAngle = Math.cos(innerAngle * (Math.PI / 180))
- const side = 2 * radius * Math.cos(innerAngle * (Math.PI / 180))
- const projection = Math.sqrt(Math.pow(radius, 2) - Math.pow(side / 2, 2))
//- Use the cut to determine how many sides get rendered and from what point
.cylinder(style=`--side: ${side}; --sides: ${sides}; --radius: ${radius}; --projection: ${projection};`)
if top
.cylinder__end.cylinder__segment.cylinder__end--top
if bottom
.cylinder__end.cylinder__segment.cylinder__end--bottom
- const [start, end] = cut
- let i = start
while i < end
.cylinder__side.cylinder__segment(style=`--index: ${i};`)
- i++
.scene
+cylinder(20, 30, [0, 30])
View Compiled
*
box-sizing border-box
transform-style preserve-3d
body
display grid
place-items center
min-height 100vh
background hsl(0, 0%, 15%)
overflow hidden
:root
--height 40
--radius 20
--animate 1
@property --rotate-x
syntax '<angle>'
inherits false
initial-value 0deg
@property --rotate-y
syntax '<angle>'
inherits false
initial-value 0deg
@property --rotate-z
syntax '<angle>'
inherits false
initial-value 0deg
.scene
transform rotateZ(calc(var(--base-rotate-z, 0) * 1deg)) rotateX(calc(var(--base-rotate-x, -24) * 1deg)) rotateY(calc(var(--base-rotate-y, -24) * 1deg)) rotateY(var(--rotate-y)) rotateX(var(--rotate-x)) rotateZ(var(--rotate-z))
animation tumble-x calc(var(--animate) * 3s) -1s infinite linear, tumble-y calc(var(--animate) * 4s) -6s infinite linear, tumble-z calc(var(--animate) * 5s) -3s infinite linear
@keyframes tumble-x
to
--rotate-x 360deg
@keyframes tumble-y
to
--rotate-y 360deg
@keyframes tumble-z
to
--rotate-z 360deg
.cylinder
--bg 'hsl(%s, 80%, 50%)' % var(--hue)
height calc(var(--height, 40) * 1vmin)
position relative
width calc(var(--radius, 40) * 2vmin)
&__segment
filter brightness(var(--b, 1))
background var(--bg, hsl(0, 80%, 50%))
position absolute
// Top
&__end
--b 1.2
top var(--t, 0)
height calc(var(--radius, 40) * 2vmin)
width calc(var(--radius, 40) * 2vmin)
border-radius 50%
transform translate(0, -50%) rotateX(90deg)
&__end--bottom
--b 0.8
--t 100%
&__side
--b 0.89
height calc(var(--height, 30) * 1vmin)
width calc(var(--side) * 1vmin)
top 0
left 50%
transform translate(-50%, 0) rotateY(calc((var(--index, 0) * 360 / var(--sides)) * 1deg)) translate3d(50%, 0, calc(var(--projection) * 1vmin))
View Compiled
import { GUI } from 'https://cdn.skypack.dev/dat.gui'
const CONFIG = {
sides: 50,
radius: 20,
height: 30,
hue: Math.random() * 360,
start: 0,
end: 50,
animate: true,
top: true,
bottom: true,
'base-rotate-x': -24,
'base-rotate-y': -24,
'base-rotate-z': 0,
}
let START
let END
const CYLINDER = document.querySelector('.cylinder')
const CTRL = new GUI()
const UPDATE = () => {
const innerAngle = (((CONFIG.sides - 2) * 180) / CONFIG.sides) * 0.5
const cosAngle = Math.cos(innerAngle * (Math.PI / 180))
const side = 2 * CONFIG.radius * cosAngle
const projection = Math.sqrt(
Math.pow(CONFIG.radius, 2) - Math.pow(side / 2, 2)
)
for (const key of Object.keys(CONFIG)) {
CYLINDER.style.setProperty(`--${key}`, CONFIG[key])
}
CYLINDER.style.setProperty('--projection', projection)
CYLINDER.style.setProperty('--side', side)
// Add the sides however you need to.
CYLINDER.innerHTML = null
const FRAG = new DocumentFragment()
if (CONFIG.top) {
const END = document.createElement('div')
END.className = 'cylinder__end cylinder__segment cylinder__end--top'
FRAG.appendChild(END)
}
if (CONFIG.bottom) {
const END = document.createElement('div')
END.className = 'cylinder__end cylinder__segment cylinder__end--bottom'
FRAG.appendChild(END)
}
for (let s = CONFIG.start; s < CONFIG.end; s++) {
const SIDE = document.createElement('div')
SIDE.className = 'cylinder__side cylinder__segment'
SIDE.style.setProperty('--index', s)
FRAG.appendChild(SIDE)
}
CYLINDER.appendChild(FRAG)
// Refresh control limits
START.max(Math.min(CONFIG.sides, CONFIG.end) - 1)
END.min(CONFIG.start + 1)
END.max(CONFIG.sides)
}
const SIZE = CTRL.addFolder('Size')
SIZE.add(CONFIG, 'radius', 2, 50, 1)
.onChange(UPDATE)
.name('Radius')
SIZE.add(CONFIG, 'height', 5, 50, 1)
.onChange(UPDATE)
.name('Height')
const CYL = CTRL.addFolder('Cylinder Config')
CYL.add(CONFIG, 'top').name('Top').onChange(UPDATE)
CYL.add(CONFIG, 'bottom').name('Bottom').onChange(UPDATE)
CYL.add(CONFIG, 'sides', 5, 100, 1)
.onChange(UPDATE)
.name('# of Sides')
START = CYL.add(CONFIG, 'start', 0, Math.min(CONFIG.sides, CONFIG.end) - 1, 1)
.onChange(UPDATE)
.name('Start Side')
END = CYL.add(CONFIG, 'end', CONFIG.start + 1, CONFIG.sides, 1)
.onChange(UPDATE)
.name('End Side')
CYL.add(CONFIG, 'hue', 0, 360, 1)
.onChange(UPDATE)
.name('Hue')
const CAM = CTRL.addFolder('Camera')
CAM.add(CONFIG, 'base-rotate-x', -360, 360, 1)
.onChange(val =>
document.documentElement.style.setProperty('--base-rotate-x', val)
)
.name('Rotate X')
CAM.add(CONFIG, 'base-rotate-y', -360, 360, 1)
.onChange(val =>
document.documentElement.style.setProperty('--base-rotate-y', val)
)
.name('Rotate Y')
CAM.add(CONFIG, 'base-rotate-z', -360, 360, 1)
.onChange(val =>
document.documentElement.style.setProperty('--base-rotate-z', val)
)
.name('Rotate Z')
CAM.add(CONFIG, 'animate')
.onChange(animate =>
document.documentElement.style.setProperty('--animate', animate ? 1 : 0)
)
.name('Animate')
UPDATE()
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.