Talking face
by Raul Bojalil
HTML
<div style="position: relative">
<div id="face" style="position: absolute" class="face"></div>
<div id="blink" style="position: absolute; display: none" class="face face-eyes-closed"></div>
</div>
CSS
img,div {
image-rendering: optimizeSpeed; /* STOP SMOOTHING, GIVE ME SPEED */
image-rendering: -moz-crisp-edges; /* Firefox */
image-rendering: -o-crisp-edges; /* Opera */
image-rendering: -webkit-optimize-contrast; /* Chrome (and eventually Safari) */
image-rendering: pixelated; /* Universal support since 2021 */
image-rendering: optimize-contrast; /* CSS3 Proposed */
-ms-interpolation-mode: nearest-neighbor; /* IE8+ */
}
.face {
width: 32px;
transform: scale(15);
height: 32px;
background: url('https://static.wixstatic.com/media/7032bc_bb6c9eaff11243a4aceb2ade7cbeef1a~mv2.png/v1/fill/w_416,h_32,al_c,lg_1,q_85,enc_auto/7032bc_bb6c9eaff11243a4aceb2ade7cbeef1a~mv2.png');
}
body {
display:flex;
flex-direction:column;
justify-content:center;
min-height:100vh;
align-content: center;
align-items: center;
height:100%;
}
.face-a {
background-position-x: 0px;
}
.face-l {
background-position-x: -32px;
}
.face-ee {
background-position-x: -64px;
}
.face-b {
background-position-x: -96px;
}
.face-f {
background-position-x: -128px;
}
.face-o {
background-position-x: -160px;
}
.face-u {
background-position-x: -192px;
}
.face-r {
background-position-x: -224px;
}
.face-c {
background-position-x: -256px;
}
.face-eyes-closed {
background-position-x: -384px;
}
JavaScript
const face = document.getElementById("face");
const blink = document.getElementById("blink");
const phonemes = {
a: "face-a",
b: "face-b",
c: "face-c",
d: "face-d",
e: "face-a",
f: "face-f",
g: "face-c",
h: "face-c",
i: "face-ee",
j: "face-c",
k: "face-c",
l: "face-l",
m: "face-b",
n: "face-c",
o: "face-o",
p: "face-b",
q: "face-c",
r: "face-r",
s: "face-c",
t: "face-c",
u: "face-u",
v: "face-b",
w: "face-u",
x: "face-c",
y: "face-c",
z: "face-c",
};
const phrase = "el akbal es tonto";
const processedPhrase = phrase.replaceAll(" ", "").toLowerCase();
const removeExpression = () => {
for(const phoneme in phonemes) {
face.classList.remove(phonemes[phoneme]);
}
}
var index = -1;
var time = 0;
var blinkTime = 0;
var startTime = window.mozAnimationStartTime || Date.now();
function animate(timestamp) {
const drawStart = (timestamp || Date.now());
var diff = drawStart - startTime;
if (diff <= 0)
{
diff = 16;
}
startTime = drawStart;
requestAnimationFrame(animate);
blinkTime += diff;
if (blinkTime > 2000) {
blink.style.display = "";
if (blinkTime > 2100) {
blink.style.display = "none";
blinkTime = 0;
}
}
if (index < processedPhrase.length) {
time += diff;
if (time > 100) {
index ++;
time = 0;
if (index < processedPhrase.length) {
const current = processedPhrase[index];
const phonemeClass = phonemes[current];
removeExpression();
face.classList.add(phonemeClass);
} else {
removeExpression();
face.classList.add(phonemes["m"])
}
}
}
}
animate();