Learn RxJS
Search…
Lockscreen
By adamlubek
This recipe demonstrates RxJS implementation of lockscreen functionality (known for example from smartphones).

Example Code

Lockscreen

index.ts

1
/*
2
Use mouse to 'swipe' across the lock pad (hold mouse button and swipe :) ).
3
Pad will turn green if password is correct or red if password is incorrect.
4
You can set password to whatever sequence you like.
5
*/
6
// RxJS v6+
7
import { from, fromEvent, Subject, merge, pipe } from 'rxjs';
8
import {
9
switchMap,
10
takeUntil,
11
repeat,
12
tap,
13
map,
14
throttleTime,
15
distinctUntilChanged,
16
filter,
17
toArray,
18
sequenceEqual,
19
pluck
20
} from 'rxjs/operators';
21
import {
22
displaySelectedNumbersSoFar,
23
markTouchedPad,
24
pads,
25
resetPasswordPad,
26
setResult
27
} from './dom-updater';
28
29
const sub = new Subject();
30
const expectedPasswordUpdate$ = fromEvent(
31
document.getElementById('expectedPassword'),
32
'keyup'
33
).pipe(
34
map((e: any) => e.target.value),
35
tap(pass => sub.next(pass.split('').map(e => parseInt(e))))
36
);
37
let expectedPassword = [1, 2, 5, 2];
38
const expectedPassword$ = sub.pipe(tap((v: any) => (expectedPassword = v)));
39
40
const takeMouseSwipe = pipe(
41
// take mouse moves
42
switchMap(_ => fromEvent(document, 'mousemove')),
43
// once mouse is up, we end swipe
44
takeUntil(fromEvent(document, 'mouseup')),
45
throttleTime(50)
46
);
47
const checkIfPasswordMatch = password =>
48
from(password).pipe(sequenceEqual(from(expectedPassword)));
49
const getXYCoordsOfMousePosition = ({ clientX, clientY }: MouseEvent) => ({
50
x: clientX,
51
y: clientY
52
});
53
const findSelectedPad = v =>
54
pads.find(
55
r => v.x > r.left && v.x < r.right && v.y > r.top && v.y < r.bottom
56
);
57
const getIdOfSelectedPad = pipe(
58
filter(v => !!v),
59
pluck('id'),
60
distinctUntilChanged()
61
);
62
63
const actualPassword$ = fromEvent(document, 'mousedown').pipe(
64
// new stream so reset password pad and take swipe until mouse up
65
tap(resetPasswordPad),
66
takeMouseSwipe,
67
// as we swipe, we mark pads as touched and display selected numbers
68
map(getXYCoordsOfMousePosition),
69
map(findSelectedPad),
70
getIdOfSelectedPad,
71
tap(markTouchedPad),
72
tap(displaySelectedNumbersSoFar),
73
// we need an array of numbers from current swipe which we can pass to checkIfPasswordMatch
74
toArray(),
75
// on mouse up (swipe end), switchMap to new stream to check if password match
76
switchMap(checkIfPasswordMatch),
77
tap(setResult),
78
// takeUntil inside takeMouseSwipe terminated stream so we repeat from beginning (mousedown)
79
repeat()
80
);
81
82
merge(expectedPassword$, expectedPasswordUpdate$, actualPassword$).subscribe();
Copied!

dom-updater.ts

1
const createPadObject = (id, rectange) => ({
2
id: id,
3
left: rectange.left,
4
right: rectange.right,
5
top: rectange.top,
6
bottom: rectange.bottom
7
});
8
9
const setResultText = text =>
10
(document.getElementById('result').innerText = text);
11
12
const setPasswordPads = color =>
13
Array.from(document.querySelectorAll('.cell')).forEach(
14
(v: HTMLElement) => (v.style.background = color)
15
);
16
17
const getPad = id => document.getElementById(`c${id}`);
18
19
export const pads = Array.from({ length: 9 }, (_, n) => n + 1).map(v =>
20
createPadObject(v, getPad(v).getBoundingClientRect())
21
);
22
23
export const markTouchedPad = v => {
24
const pad = getPad(v);
25
pad.style.background = 'lightgrey';
26
if (!pad.animate) return; //animate does not work in IE
27
const animation: any = [
28
{ transform: 'scale(0.9)' },
29
{ transform: 'scale(1)' }
30
];
31
const animationOptions = {
32
duration: 300,
33
iterations: 1
34
};
35
pad.animate(animation, animationOptions);
36
document.getSelection().removeAllRanges();
37
};
38
39
export const setResult = result => {
40
setPasswordPads(result ? 'MediumSeaGreen' : 'IndianRed');
41
setResultText('Password ' + (result ? 'matches :)' : 'does not match :('));
42
};
43
44
export const displaySelectedNumbersSoFar = v =>
45
(document.getElementById('result').textContent += v);
46
47
export const resetPasswordPad = () => {
48
setResultText('');
49
setPasswordPads('gray');
50
};
Copied!

html

1
<style>
2
.grid {
3
border-spacing: 2px;
4
}
5
.cell {
6
width: 50px;
7
height: 50px;
8
background: grey;
9
display: table-cell;
10
border-radius: 50%;
11
transform: scale(0.5);
12
text-align: center;
13
vertical-align: middle;
14
color: white;
15
}
16
17
.pulse div {
18
animation-name: pulse;
19
animation-duration: 2s;
20
animation-iteration-count: infinite;
21
}
22
23
@keyframes pulse {
24
from {
25
transform: scale(1);
26
}
27
50% {
28
transform: scale(0.99);
29
}
30
to {
31
transform: scale(1);
32
}
33
}
34
</style>
35
36
Expected Password:
37
<input id="expectedPassword" value="1252" />
38
<hr />
39
Password:
40
<div class="grid pulse">
41
<div>
42
<div class="cell" id="c1">1</div>
43
<div class="cell" id="c2">2</div>
44
<div class="cell" id="c3">3</div>
45
</div>
46
<div>
47
<div class="cell" id="c4">4</div>
48
<div class="cell" id="c5">5</div>
49
<div class="cell" id="c6">6</div>
50
</div>
51
<div>
52
<div class="cell" id="c7">7</div>
53
<div class="cell" id="c8">8</div>
54
<div class="cell" id="c9">9</div>
55
</div>
56
</div>
57
58
<div id="result"></div>
Copied!

Operators Used

Last modified 1yr ago