CSSJavaScriptUncategorized

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.

3D Room - Pure CSS Animation
3D Room – Pure CSS Animation

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
);

More Queries: