CSS 3D Mechanical Keyboard – CSS Tricks
Can you imagine? Using only CSS & a little bit help of JavaScript we can create some amazing 3d models.
Yes, in this article, we will create a 3d mechanical keyboard model using CSS and JavaScript.
Source Code:
CSS(SCSS):
$gap: 2rem;
$color-gray-100: #f1f5f9;
$color-gray-200: #e2e8f0;
$color-gray-300: #cbd5e1;
$color-gray-400: #94a3b8;
$color-gray-500: #64748b;
$color-gray-700: #334155;
$color-gray-800: #1e293b;
$color-gray-900: #0f172a;
$color-orange-300: #fbd38d;
$color-orange-400: #f6ad55;
$color-orange-500: #ed8936;
html, body {
width: 100%;
height: 100%;
font-family: 'BioRhyme', serif;
background: $color-gray-300;
display: flex;
align-items: center;
justify-content: center;
perspective: 300rem;
}
@function layered_shadow($layers, $gap_x, $gap_y, $color) {
$value: 0 0 $color;
@for $i from 1 through $layers {
$value: #{$value}, #{$i * $gap_x}rem #{$i * $gap_y}rem #{$color};
}
@return $value;
}
.keyboard {
transform: rotateX(60deg) rotateZ(45deg);
transform-style: preserve-3d;
background: $color-gray-800;
border-radius: 2rem;
padding: $gap;
box-shadow: inset 1rem 1rem 0 0.4rem $color-gray-400;
display: flex;
gap: 0 $gap;
.shade {
position: absolute;
top: 0;
left: 0;
width: 90%;
height: 5rem;
background-image: linear-gradient(90deg, $color-gray-400 50%, $color-gray-300);
transform: rotateX(90deg) rotateX(14deg) translateX(10rem) translateY(-6rem) translateZ(-39rem);
filter: blur(0.5rem);
}
.cover {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: transparent;
border-radius: 2rem;
box-shadow:
inset -0.3rem -0.3rem 0.1rem 0.2rem $color-gray-100,
inset -1rem -1rem 0 0.4rem $color-gray-300,
inset -2rem -2rem 2rem -0.6rem $color-gray-500,
layered_shadow(25, 0.3, 0.3, $color-gray-200),
8rem 10rem 2rem rgba($color-gray-900, 20%),
8rem 8rem 0.5rem rgba($color-gray-900, 20%);
}
}
.row {
display: flex;
gap: 0 $gap;
&:not(:first-child) {
filter: drop-shadow(2rem -0.5rem 0.5rem rgba($color-gray-700, 30%));
.key:not(:first-child) {
filter: drop-shadow(-0.5rem 0.5rem 0.5rem rgba($color-gray-700, 30%));
}
}
& > .key.span {
flex: 1;
.side {
width: 120%;
height: 237%;
transform: rotateZ(45deg) translate(24%, -14%) skew(337deg);
background-image: linear-gradient($color-gray-100 25%, $color-gray-300 30%);
}
.top::before {
transform: translate(5%, 5%);
}
.top::after {
background-image: linear-gradient(-25deg, transparent 45%, $color-gray-200 55%);
}
}
}
.column {
display: flex;
flex-direction: column;
gap: $gap 0;
& > .key.span {
flex: 1;
.side {
width: 220%;
height: 103%;
transform: rotateZ(45deg) translate(27%, 17%) skew(22deg);
background-image: linear-gradient($color-orange-300 70%, $color-orange-500 75%);
}
.top::before {
background-color: $color-orange-400;
transform: translate(5%, 5%);
}
.top::after {
background-image: linear-gradient(291deg, transparent 45%, $color-orange-400 50%);
}
}
&:not(:first-child) {
filter: drop-shadow(-0.5rem 0.5rem 0.5rem rgba($color-gray-700, 30%));
}
}
.key {
position: relative;
width: 6rem;
height: 6rem;
transform: translateX(-3rem) translateY(-3rem);
transform-style: preserve-3d;
user-select: none;
transition: transform 0.1s ease-out;
&.active {
transform: translateX(-1rem) translateY(-1rem);
}
.char {
position: absolute;
font-size: 4rem;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: $color-gray-500;
text-shadow:
0.05rem 0.0rem 0 $color-gray-400,
0.05rem 0.1rem 0 $color-gray-900,
0.1rem 0.05rem 0 $color-gray-400,
0.1rem 0.15rem 0 $color-gray-900,
0.15rem 0.1rem 0 $color-gray-400,
0.15rem 0.2rem 0 $color-gray-900,
0.2rem 0.15rem 0 $color-gray-400,
0.2rem 0.25rem 0 $color-gray-900,
0.25rem 0.2rem 0 $color-gray-400,
0.25rem 0.3rem 0 $color-gray-900;
}
.side {
position: absolute;
width: 250%;
height: 140%;
background-image: linear-gradient($color-gray-100 45%, $color-gray-300 55%);
border-radius: 1rem;
transform: rotateZ(45deg) translate(19%, 28%);
}
.top {
position: absolute;
width: 100%;
height: 100%;
&::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
background-color: $color-gray-200;
border-radius: 0.8em;
filter: blur(0.3rem);
transform: translate(10%, 10%);
}
&::after {
content: '';
position: absolute;
width: 105%;
height: 105%;
background-image: linear-gradient(-45deg, transparent 45%, $color-gray-200 55%);
border-radius: 0.8rem;
box-shadow: inset -0.2rem -0.2rem 0.5rem -0.2rem white, 0.2rem 0.2rem 0.5rem -0.2rem white;
}
}
}
——————————
📂 Important Links:
——————————
>> Learn Graphics Design & Make A Successful Profession.
>> Canva Makes Graphics Design Easy.
>> Start Freelancing Today & Earn Money.
>> Make Video Editing As Your Profession.
Another Article for you.
JavaScript(TypeScript)
import React, { useState, useEffect, useRef } from 'https://cdn.skypack.dev/react';
import ReactDOM from 'https://cdn.skypack.dev/react-dom';
const useSetState = (initialState = []) => {
const [state, setState] = useState(new Set(initialState));
const add = (item) => setState(state => new Set(state.add(item)));
const remove = (item) => setState(state => {
state.delete(item);
return new Set(state);
});
return { set: state, add, remove, has: char => state.has(char) };
}
const useSound = (base64string) => {
const sound = useRef(new Audio("data:audio/wav;base64," + base64string));
return {
play: () => sound.current.play(),
stop: () => {
sound.current.pause();
sound.current.currentTime = 0;
}
}
};
const Key = ({ char, span, active }: { char: string, span?: boolean, active: boolean}) => {
return (
<div className={['key', span && 'span', active && 'active'].filter(Boolean).join(' ')}>
<div className='side'/>
<div className='top'/>
<div className='char'>{char}</div>
</div>
)
}
const Column = ({ children }) => (
<div className='column'>
{children}
</div>
);
const Row = ({ children }) => (
<div className='row'>
{children}
</div>
);
const Keyboard = () => {
// Mechanical click sound 😎
const { play, stop } = useSound('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');
const { add, remove, has } = useSetState([]);
useEffect(() => {
document.addEventListener('keydown', e => { add(e.key); stop(); play(); });
document.addEventListener('keyup', e => remove(e.key));
}, []);
const keys = (chars: string[], spans: boolean[] = []) => chars.map((char, i) => (
<Key key={char} char={char} span={spans[i] || false} active={has(char)}/>
));
return (
<div className='keyboard'>
<Column>
<Row>
{keys(['7', '8', '9'])}
</Row>
<Row>
{keys(['4', '5', '6'])}
</Row>
<Row>
{keys(['1', '2', '3'])}
</Row>
<Row>
{keys(['0', '.'], [true, false])}
</Row>
</Column>
<Column>
{keys(['+', '-'], [true, true])}
</Column>
<div className='shade'/>
<div className='cover'/>
</div>
);
}
ReactDOM.render(
<Keyboard/>,
document.body
);