
kb.html
We pretty start the same way as we started earlier, the only difference being that we will be importing some additional assets (mainly icons) as well.
We add the code snippet for a text carousel at the end of our html document.
<pre class="wp-block-syntaxhighlighter-code"><!DOCTYPE html>
<head>
<title>Virtual Keyboard w/ HTML, CSS & JS</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<link rel="shortcut icon" href="/assets/favicon.ico">
<link rel="stylesheet" href="/assets/dcode.css">
<link rel="stylesheet" href="keyboard.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<h1>Virtual Keyboard w/ HTML, CSS & JS</h1>
<h3>Features</h3>
<ul>
<li>Easy to integrate</li>
<li>Responsive</li>
<li>Vanilla JS (<strong>no libraries required!</strong>)</li>
<li>Built By A Codevita Live student</li>
</ul>
<textarea class="use-keyboard-input" style="position: absolute; top: 130px; right: 30px; width: 300px;"></textarea>
<a href="http://keyboard.js">http://keyboard.js</a>
<h1>This keyboard is
<span
class="txt-rotate"
data-period="2000"
data-rotate='[ "nerdy.", "simple.", "pure JS.", "pretty.", "sleek." ]'></span>
</h1>
</body></pre>
const Keyboard = {
elements: {
main: null,
keysContainer: null,
keys: []
},
eventHandlers: {
oninput: null,
onclose: null
},
properties: {
value: "",
capsLock: false
},
init() {
// Create main elements
this.elements.main = document.createElement("div");
this.elements.keysContainer = document.createElement("div");
// Setup main elements
this.elements.main.classList.add("keyboard", "keyboard--hidden");
this.elements.keysContainer.classList.add("keyboard__keys");
this.elements.keysContainer.appendChild(this._createKeys());
this.elements.keys = this.elements.keysContainer.querySelectorAll(".keyboard__key");
// Add to DOM
this.elements.main.appendChild(this.elements.keysContainer);
document.body.appendChild(this.elements.main);
// Automatically use keyboard for elements with .use-keyboard-input
document.querySelectorAll(".use-keyboard-input").forEach(element => {
element.addEventListener("focus", () => {
this.open(element.value, currentValue => {
element.value = currentValue;
});
});
});
},
_createKeys() {
const fragment = document.createDocumentFragment();
const keyLayout = [
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "backspace",
"q", "w", "e", "r", "t", "y", "u", "i", "o", "p",
"caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", "enter",
"done", "z", "x", "c", "v", "b", "n", "m", ",", ".", "?",
"space"
];
// Creates HTML for an icon
const createIconHTML = (icon_name) => {
return `<i class="material-icons">${icon_name}</i>`;
};
keyLayout.forEach(key => {
const keyElement = document.createElement("button");
const insertLineBreak = ["backspace", "p", "enter", "?"].indexOf(key) !== -1;
// Add attributes/classes
keyElement.setAttribute("type", "button");
keyElement.classList.add("keyboard__key");
switch (key) {
case "backspace":
keyElement.classList.add("keyboard__key--wide");
keyElement.innerHTML = createIconHTML("backspace");
keyElement.addEventListener("click", () => {
this.properties.value = this.properties.value.substring(0, this.properties.value.length - 1);
this._triggerEvent("oninput");
});
break;
case "caps":
keyElement.classList.add("keyboard__key--wide", "keyboard__key--activatable");
keyElement.innerHTML = createIconHTML("keyboard_capslock");
keyElement.addEventListener("click", () => {
this._toggleCapsLock();
keyElement.classList.toggle("keyboard__key--active", this.properties.capsLock);
});
break;
case "enter":
keyElement.classList.add("keyboard__key--wide");
keyElement.innerHTML = createIconHTML("keyboard_return");
keyElement.addEventListener("click", () => {
this.properties.value += "\n";
this._triggerEvent("oninput");
});
break;
case "space":
keyElement.classList.add("keyboard__key--extra-wide");
keyElement.innerHTML = createIconHTML("space_bar");
keyElement.addEventListener("click", () => {
this.properties.value += " ";
this._triggerEvent("oninput");
});
break;
case "done":
keyElement.classList.add("keyboard__key--wide", "keyboard__key--dark");
keyElement.innerHTML = createIconHTML("check_circle");
keyElement.addEventListener("click", () => {
this.close();
this._triggerEvent("onclose");
});
break;
default:
keyElement.textContent = key.toLowerCase();
keyElement.addEventListener("click", () => {
this.properties.value += this.properties.capsLock ? key.toUpperCase() : key.toLowerCase();
this._triggerEvent("oninput");
});
break;
}
fragment.appendChild(keyElement);
if (insertLineBreak) {
fragment.appendChild(document.createElement("br"));
}
});
return fragment;
},
_triggerEvent(handlerName) {
if (typeof this.eventHandlers[handlerName] == "function") {
this.eventHandlers[handlerName](this.properties.value);
}
},
_toggleCapsLock() {
this.properties.capsLock = !this.properties.capsLock;
for (const key of this.elements.keys) {
if (key.childElementCount === 0) {
key.textContent = this.properties.capsLock ? key.textContent.toUpperCase() : key.textContent.toLowerCase();
}
}
},
open(initialValue, oninput, onclose) {
this.properties.value = initialValue || "";
this.eventHandlers.oninput = oninput;
this.eventHandlers.onclose = onclose;
this.elements.main.classList.remove("keyboard--hidden");
},
close() {
this.properties.value = "";
this.eventHandlers.oninput = oninput;
this.eventHandlers.onclose = onclose;
this.elements.main.classList.add("keyboard--hidden");
}
};
window.addEventListener("DOMContentLoaded", function () {
Keyboard.init();
});
var TxtRotate = function(el, toRotate, period) {
this.toRotate = toRotate;
this.el = el;
this.loopNum = 0;
this.period = parseInt(period, 10) || 2000;
this.txt = '';
this.tick();
this.isDeleting = false;
};
TxtRotate.prototype.tick = function() {
var i = this.loopNum % this.toRotate.length;
var fullTxt = this.toRotate[i];
if (this.isDeleting) {
this.txt = fullTxt.substring(0, this.txt.length - 1);
} else {
this.txt = fullTxt.substring(0, this.txt.length + 1);
}
this.el.innerHTML = '<span class="wrap">'+this.txt+'</span>';
var that = this;
var delta = 300 - Math.random() * 100;
if (this.isDeleting) { delta /= 2; }
if (!this.isDeleting && this.txt === fullTxt) {
delta = this.period;
this.isDeleting = true;
} else if (this.isDeleting && this.txt === '') {
this.isDeleting = false;
this.loopNum++;
delta = 500;
}
setTimeout(function() {
that.tick();
}, delta);
};
window.onload = function() {
var elements = document.getElementsByClassName('txt-rotate');
for (var i=0; i<elements.length; i++) {
var toRotate = elements[i].getAttribute('data-rotate');
var period = elements[i].getAttribute('data-period');
if (toRotate) {
new TxtRotate(elements[i], JSON.parse(toRotate), period);
}
}
// INJECT CSS
var css = document.createElement("style");
css.type = "text/css";
css.innerHTML = ".txt-rotate > .wrap { border-right: 0.08em solid #666 }";
document.body.appendChild(css);
};
keyboard.js
We render functionality to our keyboard in the first 188 lines. The code after that gives functionality to our carousel.
keyboard.css
We define the layout of our keyboard using some custom CSS elements. Feel free to tweak around the elements.
.keyboard {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding: 5px 0;
background: #004134;
box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
user-select: none;
transition: bottom 0.4s;
}
.keyboard--hidden {
bottom: -100%;
}
.keyboard__keys {
text-align: center;
}
.keyboard__key {
height: 45px;
width: 6%;
max-width: 90px;
margin: 3px;
border-radius: 4px;
border: none;
background: rgba(255, 255, 255, 0.2);
color: #ffffff;
font-size: 1.05rem;
outline: none;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: top;
padding: 0;
-webkit-tap-highlight-color: transparent;
position: relative;
}
.keyboard__key:active {
background: rgba(255, 255, 255, 0.12);
}
.keyboard__key--wide {
width: 12%;
}
.keyboard__key--extra-wide {
width: 36%;
max-width: 500px;
}
.keyboard__key--activatable::after {
content: '';
top: 10px;
right: 10px;
position: absolute;
width: 8px;
height: 8px;
background: rgba(0, 0, 0, 0.4);
border-radius: 50%;
}
.keyboard__key--active::after {
background: #08ff00;
}
.keyboard__key--dark {
background: rgba(0, 0, 0, 0.25);
}

Bonus Challenge
Modify the above script to render a new random background every time the user refreshes the page to unlock an additional module!