-
Notifications
You must be signed in to change notification settings - Fork 4
/
story-viewer.ts
138 lines (117 loc) · 3.55 KB
/
story-viewer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import { LitElement, html, css, internalProperty, customElement, PropertyValues } from 'lit-element';
import { classMap } from 'lit-html/directives/class-map';
// Gesture control
import 'hammerjs';
@customElement('story-viewer')
export class StoryViewer extends LitElement {
@internalProperty() _index: number = 0;
get index() {
return this._index;
}
set index(value: number) {
this.children[this._index].dispatchEvent(new CustomEvent('exited'));
this.children[value].dispatchEvent(new CustomEvent('entered'));
this._index = value;
}
@internalProperty() _panData: {isFinal?: boolean, deltaX?: number} = {};
constructor() {
super();
// Detect pans easily with Hammer.js
new Hammer(this).on('pan', (e: HammerInput) => this._panData = e);
this.index = 0;
}
firstUpdated() {
this.children[this._index].dispatchEvent(new CustomEvent('entered'));
}
update(changedProperties: PropertyValues) {
let { deltaX = 0, isFinal = false } = this._panData;
// Guard against an infinite loop by looking for index.
if (!changedProperties.has("_index") && isFinal) {
deltaX > 0 ? this.previous() : this.next();
}
const width = this.clientWidth;
const minScale = 0.8;
// We don't want the latent deltaX when releasing a pan.
deltaX = (isFinal ? 0 : deltaX);
Array.from(this.children).forEach((el: Element, i) => {
const x = (i - this.index) * width + deltaX;
// Piecewise scale(deltaX), looks like: __/\__
const u = deltaX / width + (i - this.index);
const v = -Math.abs(u * (1 - minScale)) + 1;
const scale = Math.max(v, minScale);
(el as HTMLElement).style.transform = `translate3d(${x}px,0,0) scale(${scale})`;
})
super.update(changedProperties);
}
/* Advance to the next story card if possible */
next() {
this.index = Math.max(0, Math.min(this.children.length - 1, this.index + 1));
}
/* Go back to the previous story card if possible */
previous() {
this.index = Math.max(0, Math.min(this.children.length - 1, this.index - 1));
}
static styles = css`
:host {
display: block;
position: relative;
align-items: center;
width: 300px;
height: 800px;
}
::slotted(*) {
position: absolute;
width: 100%;
height: calc(100% - 20px);
transition: transform 0.35s ease-out;
}
svg {
position: absolute;
top: calc(50% - 25px);
height: 50px;
cursor: pointer;
}
#next {
right: 0;
}
#progress {
position: relative;
top: calc(100% - 20px);
height: 20px;
width: 50%;
margin: 0 auto;
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
grid-gap: 10px;
align-content: center;
}
#progress > div {
background: grey;
height: 4px;
transition: background 0.3s linear;
cursor: pointer;
}
#progress > div.watched {
background: white;
}`;
render() {
return html`
<slot></slot>
<svg id="prev" viewBox="0 0 10 10" @click=${() => this.previous()}>
<path d="M 6 2 L 4 5 L 6 8" stroke="#fff" fill="none" />
</svg>
<svg id="next" viewBox="0 0 10 10" @click=${() => this.next()}>
<path d="M 4 2 L 6 5 L 4 8" stroke="#fff" fill="none" />
</svg>
<div id="progress">
${Array.from(this.children).map((_, i) => html`
<div
class=${classMap({watched: i <= this.index})}
@click=${() => this.index = i}
></div>`
)}
</div>
`;
}
}