Skip to content

Instantly share code, notes, and snippets.

@peacefullatom
Last active December 26, 2019 13:24
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save peacefullatom/19982c4637f778af9571b13f106a3e39 to your computer and use it in GitHub Desktop.
Save peacefullatom/19982c4637f778af9571b13f106a3e39 to your computer and use it in GitHub Desktop.
3D keyboard
:root {
/* keyboard width */
--width: 1000px;
/* keyboard height */
--height: 420px;
/* border radius */
--radius: 5px;
/* defines how high the button is raised */
--depth: 5px;
/* letter color */
--color: yellow;
}
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
body {
/* centering the content */
display: flex;
align-items: center;
justify-content: center;
/* adding a gradient background */
background-image: linear-gradient(to bottom, hsl(225 25% 20%) 0%, hsl(225 40% 10%) 100%);
}
#container {
/* the perspective is equal to the initial keyboard width */
perspective: var(--width);
}
.keyboard {
/* spreading sections evenly */
display: flex;
justify-content: space-between;
/* setting the size */
width: var(--width);
height: var(--height);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
/* adding a gradient background */
background-image: linear-gradient(to bottom, hsl(192 11% 53%) 0%, hsl(192 26% 43%) 100%);
/* setting the border radius */
border-radius: var(--radius);
/* calculating paddings */
padding: calc(var(--radius) * 2);
box-sizing: border-box;
/* enabling the 3d mode */
transform-style: preserve-3d;
/* applying the transform rule */
transform: rotateX(0.13turn) rotateY(0turn) rotateZ(0turn);
}
.overlay {
/* setting the size */
width: var(--width);
height: var(--height);
/* centering the overlay */
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%) translateZ(10px);
/* adding a gradient background */
background-image: linear-gradient(to bottom, #ffffff33 0%, transparent 100%);
/* adding a noisy effect */
filter: blur(25px);
}
.section {
/* spreading rows evenly */
display: flex;
flex-direction: column;
justify-content: space-between;
}
.row {
/* spreading buttons evenly */
display: flex;
justify-content: space-between;
}
.row.functions .button {
/* calculating the height of the function button */
height: calc(var(--height) / 10);
}
.row.actions .button,
.row.functions .button {
/* calculating the width of the action and function buttons */
--size: calc(var(--width) / 18);
}
.button {
/* setting the default dimensions of the button */
--size: calc(var(--width) / 20);
height: calc(var(--height) / 7);
width: var(--size);
/* setting the border radius */
border-radius: var(--radius);
/* centering the content of the button */
display: flex;
justify-content: center;
align-items: center;
/* additional settings */
box-sizing: border-box;
background: #000000;
/* applying the global color */
color: var(--color);
/* adding the default margin */
margin-left: calc(var(--width) / 200);
/* raising the button above the keyboard */
transform: translate3d(0px, 0px, var(--depth));
/* enabling the 3d mode */
transform-style: preserve-3d;
/* calculating the perspective from the width */
perspective: calc(var(--size) * 3);
}
.button:first-child {
/* reset margin for the leftmost button */
margin-left: 0;
}
.button .shadow {
/* centering the shadow */
position: absolute;
left: 50%;
top: 50%;
/* applying the transform */
transform: translate3d(-50%, -50%, calc(var(--depth) * -1));
background: #00000088;
}
.button.multi {
/* placing labels under each other */
flex-direction: column-reverse;
}
/* the next set of rules are buttons adjustments */
.button.backspace,
.button.tab {
--size: calc(var(--width) / 13);
}
.button.backspace {
font-size: 0.8rem;
}
.button.capslock,
.button.enter {
--size: calc(var(--width) / 11);
}
.button.shift {
--size: calc(var(--width) / 9);
}
.button.space {
--size: calc(var(--width) / 2.3);
}
/* settings for the special button */
.button.dev {
/* defining the accent color */
--accent: #ffffff;
color: var(--accent);
/* adjusting letter spacing for the better readability */
letter-spacing: 0.5px;
/* adding the glow effect */
text-shadow: 0 0 5px var(--accent), 0 0 10px var(--accent), 0 0 15px var(--accent), 0 0 20px var(--color), 0 0 30px var(--color),
0 0 40px var(--color), 0 0 50px var(--color), 0 0 75px var(--color);
}
/* the empty button settings */
.button.empty {
background: transparent;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Keyboard</title>
<link rel="stylesheet" type="text/css" href="keyboard.css" />
</head>
<body>
<div id="container"></div>
<script src="keyboard.js"></script>
</body>
</html>
/**
* @typedef {Object} key
* @property {string} [extra] extra class name
* @property {string | string[]} value button label(s)
*/
/**
* @typedef {Object} section
* @property {string} [extra] extra class name
* @property {key[]} keys set of keys in the row
*/
/**
* the list of buttons of the main section
* @type {section[]}
*/
const mainSection = [
{
extra: 'functions',
keys: [
{ value: 'Esc' },
{ value: 'F1' },
{ value: 'F2' },
{ value: 'F3' },
{ value: 'F4' },
{ value: 'F5' },
{ value: 'F6' },
{ value: 'F7' },
{ value: 'F8' },
{ value: 'F9' },
{ value: 'F10' },
{ value: 'F11' },
{ value: 'F12' }
]
},
{
keys: [
{ value: ['`', `~`] },
{ value: ['1', '!'] },
{ value: ['2', '@'] },
{ value: ['3', '#'] },
{ value: ['4', '$'] },
{ value: ['5', '%'] },
{ value: ['6', '^'] },
{ value: ['7', '&'] },
{ value: ['8', '*'] },
{ value: ['9', '('] },
{ value: ['0', ')'] },
{ value: ['-', '_'] },
{ value: ['=', '+'] },
{ value: 'Backspace', extra: 'backspace' }
]
},
{
keys: [
{ value: 'Tab', extra: 'tab' },
{ value: 'Q' },
{ value: 'W' },
{ value: 'E' },
{ value: 'R' },
{ value: 'T' },
{ value: 'Y' },
{ value: 'U' },
{ value: 'I' },
{ value: 'O' },
{ value: 'P' },
{ value: ['[', '{'] },
{ value: [']', '}'] },
{ value: ['\\', '|'] }
]
},
{
keys: [
{ value: 'Caps Lock', extra: 'capslock' },
{ value: 'A' },
{ value: 'S' },
{ value: 'D' },
{ value: 'F' },
{ value: 'G' },
{ value: 'H' },
{ value: 'J' },
{ value: 'K' },
{ value: 'L' },
{ value: [';', ':'] },
{ value: ["'", '"'] },
{ value: 'Enter', extra: 'enter' }
]
},
{
keys: [
{ value: 'Shift', extra: 'shift' },
{ value: 'Z' },
{ value: 'X' },
{ value: 'C' },
{ value: 'V' },
{ value: 'B' },
{ value: 'N' },
{ value: 'M' },
{ value: [',', '<'] },
{ value: ['.', '>'] },
{ value: ['/', '?'] },
{ value: 'Shift', extra: 'shift' }
]
},
{
keys: [
{ value: 'Ctrl' },
{ value: 'Fn' },
{ value: '[DEV]', extra: 'dev' },
{ value: 'Alt' },
{ value: 'Space', extra: 'space' },
{ value: 'Alt' },
{ value: 'Ctrl' }
]
}
];
/**
* the list of buttons of the additional section
* @type {section[]}
*/
const additionalSection = [
{ extra: 'functions', keys: [{ value: 'PrtScr' }, { value: 'ScrLk' }, { value: 'Pause' }] },
{ extra: 'actions', keys: [{ value: 'Ins' }, { value: 'Home' }, { value: 'PgUp' }] },
{ extra: 'actions', keys: [{ value: 'Del' }, { value: 'End' }, { value: 'PgDn' }] },
{ extra: 'actions', keys: [{ extra: 'empty' }] },
{ extra: 'actions', keys: [{ extra: 'empty' }, { value: '\u2191' }, { extra: 'empty' }] },
{ extra: 'actions', keys: [{ value: '\u2190' }, { value: '\u2193' }, { value: '\u2192' }] }
];
/**
* parse the array of strings and build a string from the values
* @param {string[]} values values to be parsed
* @returns {string}
*/
function toString(values) {
return values.filter(value => !!value).join(' ');
}
/**
* create new div element
* @returns {HTMLDivElement}
*/
function div() {
return document.createElement('div');
}
/**
* draw a section
* @param {section[][]} sections list of sections to be drawn
*/
function draw(sections) {
// obtaining the container
const container = document.getElementById('container');
if (container) {
// creating keyboard
const keyboard = div();
keyboard.className = 'keyboard';
sections.forEach(rows => {
// creating section
const section = div();
section.className = 'section';
rows.forEach(data => {
// creating row
const row = div();
row.className = toString(['row', data.extra]);
if (data.keys instanceof Array) {
data.keys.forEach(key => {
// creating button
const button = div();
// creating shadow
const shadow = div();
const value = key.value instanceof Array ? key.value : [key.value];
// setting classes
button.className = toString(['button', key.extra, value.length > 1 ? 'multi' : null]);
shadow.className = toString(['button', key.extra, 'shadow']);
// appending the shadow
button.appendChild(shadow);
// rendering labels
value.forEach(item => {
const label = div();
label.innerText = item || '';
button.appendChild(label);
});
// appending the button to the row
row.appendChild(button);
});
}
// appending the row to the section
section.appendChild(row);
});
// appending the section to the keyboard
keyboard.appendChild(section);
});
// adding the overlay
const overlay = div();
overlay.className = 'overlay';
keyboard.appendChild(overlay);
// appending the keyboard to the container
container.appendChild(keyboard);
}
}
/** draw the keyboard */
draw([mainSection, additionalSection]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment