<div class="container">
	<div class="txt-wrapper">
		<div class="emotionTxt__wrap">
			<p class="emotionTxt">Loading</p>
		</div>
		<div class="line"></div>
		<h1 class="txt">SAP</h1>
	</div>
	<div id="vid-wrapper" class="vid-wrapper">
		<video id="video" class="video" autoplay muted></video>
	</div>
</div>
@import url('https://fonts.googleapis.com/css?family=Roboto+Slab&display=block');

@font-face {
	font-family: 'Anybody';
	src: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/61488/ETCAnybodyPB.woff2') format('woff2-variations');
	font-display: block;
	font-style: normal italic;
	font-weight: 100 900;
	font-stretch: 10% 400%;
}

/*  ========================================================================== 
	Responsive Viewport Sized Typography
	========================================================================== */
	
///
/// Viewport sized typography with minimum and maximum values
///
/// @author Eduardo Boucas (@eduardoboucas)
///
/// @param {Number}   $responsive  - Viewport-based size
/// @param {Number}   $min         - Minimum font size (px)
/// @param {Number}   $max         - Maximum font size (px)
///                                  (optional)
/// @param {Number}   $fallback    - Fallback for viewport-
///                                  based units (optional)
///
/// @example scss - 5vw font size (with 50px fallback), 
///                 minumum of 35px and maximum of 150px
///  @include responsive-font(5vw, 35px, 150px, 50px);
///
@mixin responsive-font($responsive, $min, $max: false, $fallback: false) {
	$responsive-unitless: $responsive / ($responsive - $responsive + 1);
	$dimension: if(unit($responsive)=='vh', 'height', 'width');
	$min-breakpoint: $min / $responsive-unitless * 100;
	@media (max-#{$dimension}: #{$min-breakpoint}) {
		font-size: $min;
	}
	@if $max {
		$max-breakpoint: $max / $responsive-unitless * 100;
		@media (min-#{$dimension}: #{$max-breakpoint}) {
			font-size: $max;
		}
	}
	@if $fallback {
		font-size: $fallback;
	}
	font-size: $responsive;
};

html {
	box-sizing: border-box;
}

*, *:before, *:after {
	box-sizing: inherit;
}

html, body {
	width: 100%;
	height: 100%;
}

body {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100vw;
	height: 100vh;
	margin: 0;
	padding: 0;
	background-color: black;
	color: white;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	overflow: hidden;
}

.container {
	display: flex;
	width: 100vw;
	height: 100vh;
	align-items: center;
	justify-content: center;
}

.txt-wrapper {
	position: relative;
	z-index: 10;
	text-transform: uppercase;
	visibility: hidden;
}

.emotionTxt__wrap {
	overflow: hidden;
	padding-bottom: 8px;
}

.emotionTxt {
	margin: 0;
	font-family: 'Roboto Slab', serif;
	font-weight: 400;
	// font-size: 1.3vw;
	@include responsive-font(1.3vw, 12px, 27px, 16px);
	text-align: center;
	letter-spacing: 0px;
}

.line {
	display: block;
	width: 100%;
	height: 1px;
	background: white;
}

.txt {
	margin: 8px 0 0;
	@include responsive-font(15vw, 100px, 300px, 100px);
	// font-size: 15vw;
	line-height: 0.8;
	font-family: 'Anybody';
	text-align: center;
	font-weight: 100;
	font-stretch: 10%;
	// overflow: hidden;
}

.char {
	overflow: visible;
}

.vid-wrapper {
	position: absolute;
	z-index: 1;
	top: 0;
	right: 0;
	width: 100%;
	height: 100%;
}

canvas {
	position: absolute;
	z-index: 2;
	top: 0;
	right: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0.5
}

video {
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0.2;
}
View Compiled
console.clear();

select = e => document.querySelector(e);
selectAll = e => document.querySelectorAll(e);

const models = "https://petebarr.github.io/Face-Detection-JavaScript/models";
const video = select(".video");
const vidWrap = select(".vid-wrapper");
const txtWrap = select(".txt-wrapper");
const emotionTxt = select(".emotionTxt");
const txt = select(".txt");
const line = select(".line");

let neutral = 1,
	happy = 0,
	sad = 0,
	surprised = 0,
	angry = 0,
	emo = "Neutral";

function initFaceDetection() {
	Promise.all([
		faceapi.nets.tinyFaceDetector.loadFromUri(models),
		faceapi.nets.faceLandmark68Net.loadFromUri(models),
		faceapi.nets.faceRecognitionNet.loadFromUri(models),
		faceapi.nets.faceExpressionNet.loadFromUri(models),
	]).then(startVideo)
}

function startVideo() {
	navigator.mediaDevices.getUserMedia({ audio: false, video: true })
	.then( stream => {
		var video = document.querySelector('video');
		video.srcObject = stream;
		video.onloadedmetadata = function(e) {
			video.play();
		};
	})
}

video.addEventListener('play', () => {
	let vW = video.videoWidth;
	let vH = video.videoHeight;
	let aspectRatio = vW/vH;
	const displaySize = { width: video.offsetWidth, height: video.offsetWidth/aspectRatio };
	const canvas = faceapi.createCanvasFromMedia(video);
	const ctx = canvas.getContext('2d');
	vidWrap.append(canvas);
	faceapi.matchDimensions(canvas, displaySize);
	setInterval(async () => {
		const detections = await faceapi.detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
			.withFaceLandmarks()
			.withFaceExpressions();
		const resizedDetections = faceapi.resizeResults(detections, displaySize);
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		
		// faceapi.draw.drawDetections(canvas, resizedDetections);
		faceapi.draw.drawFaceLandmarks(canvas, resizedDetections);
		// faceapi.draw.drawFaceExpressions(canvas, resizedDetections);
		
		happy = resizedDetections[0].expressions.happy > 0.8 ? 1 : 0;
		sad = resizedDetections[0].expressions.sad > 0.8 ? 1 : 0;
		angry = resizedDetections[0].expressions.angry > 0.8 ? 1 : 0;
		surprised = resizedDetections[0].expressions.surprised > 0.8 ? 1 : 0;
		neutral = resizedDetections[0].expressions.neutral > 0.8 ? 1 : 0;

		animateText();
	}, 100);
	
})

function animateText() {
	let fw = 100,
		fs = 10,
		fsl = 0;
	
	if(happy==1) {
		fw = 500;
		fs = 200;
		fsl = 0;
		setEmo("Happy");
	}
	else if(sad == 1) {
		fw = 900;
		fs = 50;
		fsl = 0;
		setEmo("Sad");
	}
	else if(surprised == 1) {
		fw = 900;
		fs = 400;
		fsl = 50;
		setEmo("Surprised");
	}
	else if(angry == 1) {
		fw = 900;
		fs = 10;
		fsl = 100;
		setEmo("Angry");
	}
	
	else { // neutral
		fw = 100;
		fs = 10;
		fsl = 0;
		setEmo("Boring");
	}
	
	gsap.to( '.txt', {
		fontWeight: fw,
		fontStretch: fs,
		fontVariationSettings: () => {
			return `'slnt' ${fsl}`;
		},
		ease: "power2",
		duration: 0.4
	} );
}

function setEmo(emo) {
	emotionTxt.textContent = emo;
}

function init() {
	let tSplitText = new SplitText(txt, {type:"chars", charsClass:"char" }); 
	gsap.timeline({ delay: 0.5, onComplete: initFaceDetection })
		.set(txtWrap, { autoAlpha: 1 })
		.from(emotionTxt, { y: 50, duration: 0.4, ease: "power4"}, 0)
		.from(line, { scaleX: 0, duration: 0.4, ease: "power4", transformOrigin: "center center"}, 0.2)
		.from(".char", { 
			y: 300,
			opacity: 0,
			duration: 0.6,
			ease: "elastic(1, 1.5)",
			stagger:  0.1
		}, 0.1)
}

window.onload = () => {
	init();
};

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/gsap-latest-beta.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.0.0/gsap.min.js
  3. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/SplitText3.min.js
  4. https://s3-us-west-2.amazonaws.com/s.cdpn.io/61488/face-api.min.js