Improve video UX

This commit is contained in:
schmelczerandras 2020-11-27 12:17:47 +01:00
parent 08feeb6bfc
commit f3c8453782
13 changed files with 101 additions and 69 deletions

View file

@ -1,16 +1,15 @@
import adAstraPoster from '../static/media/ad_astra.jpg?format=jpg'; import adAstraPoster from '../static/media/ad_astra.jpg?format=jpg';
import adAstraMp4 from '../static/media/ad_astra_720.mp4'; import adAstraMp4 from '../static/media/mp4/ad_astra.mp4';
import adAstraWebM from '../static/media/ad_astra_720.webm'; import adAstraWebM from '../static/media/webm/ad_astra.webm';
import { GitHub } from './shared'; import { GitHub } from './shared';
import { last } from '../helper/last';
import { Video } from '../page/basics/video/video'; import { Video } from '../page/basics/video/video';
export const adAstraTimelineElement = { export const adAstraTimelineElement = {
title: `Gaming on an ATtiny85`, title: `Gaming on an ATtiny85`,
date: `2020 Spring`, date: `2020 Spring`,
figure: new Video({ figure: new Video({
poster: last(adAstraPoster.images)!.path, poster: adAstraPoster,
mp4: adAstraMp4, mp4: adAstraMp4,
webm: adAstraWebM, webm: adAstraWebM,
}), }),

View file

@ -1,15 +1,14 @@
import citySimulationPoster from '../static/media/simulation.jpg?format=jpg'; import citySimulationPoster from '../static/media/simulation.jpg?format=jpg';
import citySimulationWebM from '../static/media/simulation.webm'; import citySimulationMp4 from '../static/media/mp4/simulation.mp4';
import citySimulationMp4 from '../static/media/simulation.mp4'; import citySimulationWebM from '../static/media/webm/simulation.webm';
import { Video } from '../page/basics/video/video'; import { Video } from '../page/basics/video/video';
import { last } from '../helper/last';
export const citySimulationTimelineElement = { export const citySimulationTimelineElement = {
date: `2018 July - August`, date: `2018 July - August`,
title: `City simulation`, title: `City simulation`,
figure: new Video({ figure: new Video({
poster: last(citySimulationPoster.images)!.path, poster: citySimulationPoster,
mp4: citySimulationMp4, mp4: citySimulationMp4,
webm: citySimulationWebM, webm: citySimulationWebM,
}), }),

View file

@ -1,14 +1,17 @@
import forexMp4 from '../static/media/forex.mp4'; import forexMp4 from '../static/media/mp4/forex.mp4';
import forexWebM from '../static/media/forex.webm'; import forexWebM from '../static/media/webm/forex.webm';
import forexPoster from '../static/media/forex.jpg';
import { Video } from '../page/basics/video/video'; import { Video } from '../page/basics/video/video';
export const forexTimelineElement = { export const forexTimelineElement = {
title: `Predicting foreign exchange rates`, title: `Predicting foreign exchange rates`,
date: `2019 Autumn`, date: `2019 Autumn`,
figure: new Video({ figure: new Video({
poster: forexPoster,
mp4: forexMp4, mp4: forexMp4,
webm: forexWebM, webm: forexWebM,
shouldActLikeGif: true, invertButton: true,
}), }),
description: ` description: `
From the animation, we can see that my implementation does a somewhat acceptable job at From the animation, we can see that my implementation does a somewhat acceptable job at

View file

@ -1,15 +1,14 @@
import ledPoster from '../static/media/led.jpg?format=jpg'; import ledPoster from '../static/media/led.jpg?format=jpg';
import ledMp4 from '../static/media/led.mp4'; import ledMp4 from '../static/media/mp4/led.mp4';
import ledWebM from '../static/media/led.webm'; import ledWebM from '../static/media/webm/led.webm';
import { last } from '../helper/last';
import { Video } from '../page/basics/video/video'; import { Video } from '../page/basics/video/video';
export const ledsTimelineElement = { export const ledsTimelineElement = {
date: `2016 spring`, date: `2016 spring`,
title: `Lights synchronised to music`, title: `Lights synchronised to music`,
figure: new Video({ figure: new Video({
poster: last(ledPoster.images)!.path, poster: ledPoster,
mp4: ledMp4, mp4: ledMp4,
webm: ledWebM, webm: ledWebM,
}), }),

View file

@ -7,6 +7,7 @@
top: 0; top: 0;
border-radius: 1000px; border-radius: 1000px;
transition: background-color var(--transition-time); transition: background-color var(--transition-time);
transform: translateX(-100%);
&:nth-child(odd) { &:nth-child(odd) {
background-color: #fff9e0; background-color: #fff9e0;

View file

@ -16,7 +16,13 @@ export const generate = ({
alt: string; alt: string;
container: boolean; container: boolean;
}): html => ` }): html => `
${container ? `<div class="figure-container">` : ''} ${
container
? `<div class="figure-container" style="padding-top:${(imageJpeg.height /
imageJpeg.width) *
100}%">`
: ''
}
<picture loading="lazy"> <picture loading="lazy">
<source <source
srcset="${imageWebP.srcSet}" srcset="${imageWebP.srcSet}"

View file

@ -8,8 +8,8 @@ export const generate = ({ alt }: { alt: string }): html => `
<img image-viewer-ignore class="poster" /> <img image-viewer-ignore class="poster" />
<div class="overlay"> <div class="overlay">
<div class="loading">${loading}</div> <div class="loading">${loading}</div>
<iframe title="${alt}" height=300 allowfullscreen loading="lazy"></iframe> <iframe title="${alt}" allowfullscreen loading="lazy"></iframe>
<div class="load-button">${play}</div> <div class="start-button">${play}</div>
</div> </div>
</div> </div>
`; `;

View file

@ -9,27 +9,9 @@
left: 0; left: 0;
top: 0; top: 0;
* { iframe {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0;
}
.load-button {
@include image-button(var(--large-icon-size));
@include absolute-center;
@include square(calc(var(--large-icon-size) + var(--normal-margin) * 2));
&:hover svg {
box-shadow: var(--shadow);
}
svg {
border-radius: 10000px;
backdrop-filter: blur(16px);
transition: transform var(--transition-time), box-shadow var(--transition-time);
stroke: var(--normal-text-color);
}
} }
.loading { .loading {
@ -54,9 +36,10 @@
&.loaded { &.loaded {
.figure-container, .figure-container,
.load-button { .start-button {
visibility: hidden; visibility: hidden;
} }
.loading { .loading {
visibility: visible; visibility: visible;
} }

View file

@ -14,7 +14,7 @@ export class Preview extends PageElement {
super(createElement(generate({ alt }))); super(createElement(generate({ alt })));
this.url += '?portfolioView'; this.url += '?portfolioView';
this.attachElementByReplacing('.poster', new Image(posterWebP, posterJpeg, alt)); this.attachElementByReplacing('.poster', new Image(posterWebP, posterJpeg, alt));
this.query('.load-button').addEventListener('click', this.loadContent.bind(this)); this.query('.start-button').addEventListener('click', this.loadContent.bind(this));
} }
public setParent(parent: PageElement) { public setParent(parent: PageElement) {

View file

@ -1,26 +1,17 @@
import './video.scss'; import './video.scss';
import { url } from '../../../types/url';
import { html } from '../../../types/html';
export const generate = ({ import { html } from '../../../types/html';
webm, import play from '../../../static/icons/play-button.svg';
mp4, import { VideoParameters } from './video';
poster, import { last } from '../../../helper/last';
shouldActLikeGif,
container, export const generate = ({ webm, mp4, poster, invertButton }: VideoParameters): html => `
}: { <div class="figure-container" style="padding-top:${(poster.height / poster.width) *
webm: url; 100}%">
mp4: url; <video playsinline preload="none" poster="${last(poster.images)!.path}">
poster?: url;
shouldActLikeGif?: boolean;
container?: boolean;
}): html => `
${container === undefined || container ? `<div class="figure-container">` : ''}
<video loading="lazy" ${
shouldActLikeGif ? 'autoplay loop muted' : ''
} controls playsinline preload="metadata" ${poster ? `poster="${poster}` : ''}" >
<source src="${webm}" type="video/webm"/> <source src="${webm}" type="video/webm"/>
<source src="${mp4}" type="video/mp4"/> <source src="${mp4}" type="video/mp4"/>
</video> </video>
${container === undefined || container ? `</div>` : ''} <div class="start-button ${invertButton ? 'inverted' : ''}">${play}</div>
</div>
`; `;

View file

@ -0,0 +1 @@
@use '../../../style/mixins' as *;

View file

@ -2,15 +2,40 @@ import { PageElement } from '../../page-element';
import { createElement } from '../../../helper/create-element'; import { createElement } from '../../../helper/create-element';
import { generate } from './video.html'; import { generate } from './video.html';
import { url } from '../../../types/url'; import { url } from '../../../types/url';
import { ResponsiveImage } from '../../../types/responsive-image';
export interface VideoParameters {
mp4: url;
webm: url;
poster: ResponsiveImage;
invertButton?: boolean;
}
export class Video extends PageElement { export class Video extends PageElement {
public constructor(options: { private video: HTMLVideoElement;
poster?: url;
mp4: url; public constructor(options: VideoParameters) {
webm: url;
shouldActLikeGif?: boolean;
container?: boolean;
}) {
super(createElement(generate(options))); super(createElement(generate(options)));
this.video = this.query('video') as HTMLVideoElement;
this.video.addEventListener('click', this.startVideo.bind(this));
this.query('.start-button').addEventListener('click', this.startVideo.bind(this));
this.video.addEventListener('pause', this.stopVideo.bind(this));
}
private startVideo(e: Event) {
if (this.video.paused) {
this.query('.start-button').style.visibility = 'hidden';
this.video.play();
this.video.controls = true;
e.preventDefault();
}
}
private stopVideo(e: Event) {
if (this.video.paused) {
this.query('.start-button').style.visibility = 'visible';
this.video.controls = false;
e.preventDefault();
}
} }
} }

View file

@ -51,6 +51,26 @@ noscript {
@include center-children(); @include center-children();
} }
.start-button {
@include image-button(var(--large-icon-size));
@include absolute-center;
@include square(calc(var(--large-icon-size) + var(--normal-margin) * 2));
&:hover svg {
box-shadow: var(--shadow);
}
svg {
border-radius: 10000px;
backdrop-filter: blur(16px);
transition: transform var(--transition-time), box-shadow var(--transition-time);
}
&.inverted svg {
fill: var(--accent-color);
}
}
.figure-container { .figure-container {
font-size: 0; font-size: 0;
box-shadow: var(--inset-shadow); box-shadow: var(--inset-shadow);
@ -59,14 +79,19 @@ noscript {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
* {
pointer-events: auto;
}
img, img,
video, video,
iframe { iframe {
pointer-events: auto;
position: relative;
z-index: -1; z-index: -1;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%; width: 100%;
height: auto;
} }
} }