Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mika4president/b96d3f1935fd6e480876117b485616f0 to your computer and use it in GitHub Desktop.
Save mika4president/b96d3f1935fd6e480876117b485616f0 to your computer and use it in GitHub Desktop.
Expressive Robot Face for Tablet
<div class="buttons">
<button onclick="eyes.startBlinking()">Start Blinking</button>
<button onclick="eyes.stopBlinking()">Stop Blinking</button>
<button onclick="eyes.blink()" >Blink</button>
<button onclick="eyes.express({type: 'happy'})" >Happy</button>
<button onclick="eyes.express({type: 'sad'})" >Sad</button>
<button onclick="eyes.express({type: 'angry'})" >Mad</button>
<button onclick="eyes.express({type: 'focused'})" >Focused</button>
<button onclick="eyes.express({type: 'confused'})" >Confused</button>
</div>
<div class="face">
<div class="eye left">
<div class="eyelid upper"></div>
<div class="eyelid lower"></div>
</div>
<div class="eye right">
<div class="eyelid upper"></div>
<div class="eyelid lower"></div>
</div>
</div>
class EyeController {
constructor(elements = {}, eyeSize = '33.33vmin') {
this._eyeSize = eyeSize;
this._blinkTimeoutID = null;
this.setElements(elements);
}
get leftEye() { return this._leftEye; }
get rightEye() { return this._rightEye; }
setElements({
leftEye,
rightEye,
upperLeftEyelid,
upperRightEyelid,
lowerLeftEyelid,
lowerRightEyelid,
} = {}) {
this._leftEye = leftEye;
this._rightEye = rightEye;
this._upperLeftEyelid = upperLeftEyelid;
this._upperRightEyelid = upperRightEyelid;
this._lowerLeftEyelid = lowerLeftEyelid;
this._lowerRightEyelid = lowerRightEyelid;
return this;
}
_createKeyframes ({
tgtTranYVal = 0,
tgtRotVal = 0,
enteredOffset = 1/3,
exitingOffset = 2/3,
} = {}) {
return [
{transform: `translateY(0px) rotate(0deg)`, offset: 0.0},
{transform: `translateY(${tgtTranYVal}) rotate(${tgtRotVal})`, offset: enteredOffset},
{transform: `translateY(${tgtTranYVal}) rotate(${tgtRotVal})`, offset: exitingOffset},
{transform: `translateY(0px) rotate(0deg)`, offset: 1.0},
];
}
express({
type = '',
// level = 3, // 1: min, 5: max
duration = 1000,
enterDuration = 75,
exitDuration = 75,
}) {
if (!this._leftEye) { // assumes all elements are always set together
console.warn('Eye elements are not set; return;');
return;
}
const options = {
duration: duration,
};
switch(type) {
case 'happy':
return {
lowerLeftEyelid: this._lowerLeftEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * -2 / 3)`,
tgtRotVal: `30deg`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
lowerRightEyelid: this._lowerRightEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * -2 / 3)`,
tgtRotVal: `-30deg`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
};
case 'sad':
return {
upperLeftEyelid: this._upperLeftEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * 1 / 3)`,
tgtRotVal: `-20deg`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
upperRightEyelid: this._upperRightEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * 1 / 3)`,
tgtRotVal: `20deg`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
};
case 'angry':
return {
upperLeftEyelid: this._upperLeftEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * 1 / 4)`,
tgtRotVal: `30deg`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
upperRightEyelid: this._upperRightEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * 1 / 4)`,
tgtRotVal: `-30deg`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
};
case 'focused':
return {
upperLeftEyelid: this._upperLeftEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * 1 / 3)`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
upperRightEyelid: this._upperRightEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * 1 / 3)`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
lowerLeftEyelid: this._lowerLeftEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * -1 / 3)`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
lowerRightEyelid: this._lowerRightEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * -1 / 3)`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
}
case 'confused':
return {
upperRightEyelid: this._upperRightEyelid.animate(this._createKeyframes({
tgtTranYVal: `calc(${this._eyeSize} * 1 / 3)`,
tgtRotVal: `-10deg`,
enteredOffset: enterDuration / duration,
exitingOffset: 1 - (exitDuration / duration),
}), options),
}
default:
console.warn(`Invalid input type=${type}`);
}
}
blink({
duration = 150, // in ms
} = {}) {
if (!this._leftEye) { // assumes all elements are always set together
console.warn('Eye elements are not set; return;');
return;
}
[this._leftEye, this._rightEye].map((eye) => {
eye.animate([
{transform: 'rotateX(0deg)'},
{transform: 'rotateX(90deg)'},
{transform: 'rotateX(0deg)'},
], {
duration,
iterations: 1,
});
});
}
startBlinking({
maxInterval = 5000
} = {}) {
if (this._blinkTimeoutID) {
console.warn(`Already blinking with timeoutID=${this._blinkTimeoutID}; return;`);
return;
}
const blinkRandomly = (timeout) => {
this._blinkTimeoutID = setTimeout(() => {
this.blink();
blinkRandomly(Math.random() * maxInterval);
}, timeout);
}
blinkRandomly(Math.random() * maxInterval);
}
stopBlinking() {
clearTimeout(this._blinkTimeoutID);
this._blinkTimeoutID = null;
}
setEyePosition(eyeElem, x, y, isRight = false) {
if (!eyeElem) { // assumes all elements are always set together
console.warn('Invalid inputs ', eyeElem, x, y, '; retuning');
return;
}
if (!!x) {
if (!isRight) {
eyeElem.style.left = `calc(${this._eyeSize} / 3 * 2 * ${x})`;
} else {
eyeElem.style.right = `calc(${this._eyeSize} / 3 * 2 * ${1-x})`;
}
}
if (!!y) {
eyeElem.style.bottom = `calc(${this._eyeSize} / 3 * 2 * ${1-y})`;
}
}
}
const eyes = new EyeController({
leftEye: document.querySelector('.left.eye'),
rightEye: document.querySelector('.right.eye'),
upperLeftEyelid: document.querySelector('.left .eyelid.upper'),
upperRightEyelid: document.querySelector('.right .eyelid.upper'),
lowerLeftEyelid: document.querySelector('.left .eyelid.lower'),
lowerRightEyelid: document.querySelector('.right .eyelid.lower'),
});
:root {
--face-color: whitesmoke;
--face-height: 100vh;
--face-width: 100vw;
--eye-size: 33.33vmin;
--eye-color: black;
--eyelid-color: whitesmoke;
}
body {
background-color: white;
margin: 0px;
}
.buttons {
position: relative;
z-index: 3;
}
.face {
background-color: var(--face-color);
margin: auto;
height: var(--face-height);
width: var(--face-width);
position: relative;
overflow: hidden;
}
.face div {
position: absolute;
}
.eye {
background-color: var(--eye-color);
border-radius: 100%;
height: var(--eye-size);
width: var(--eye-size);
bottom: calc(var(--eye-size) / 3);
z-index: 1;
transform: rotateX(0);
}
.eye.left {
left: calc(var(--eye-size) / 3);
}
.eye.right {
right: calc(var(--eye-size) / 3);
}
.eyelid {
background-color: var(--eyelid-color);
height: var(--eye-size);
width: calc(var(--eye-size) * 1.75);
z-index: 2;
transform: rotate(0deg);
}
.eyelid.upper {
bottom: calc(var(--eye-size) * 1);
left: calc(var(--eye-size) * -0.375);
}
.eyelid.lower {
border-radius: 100%;
bottom: calc(var(--eye-size) * -1);
left: calc(var(--eye-size) * -0.375);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment