I'm building an audio playback control that lets users scrub back and forth through an audio file. It needs to work with touch and mouse events. How should I go about managing the events for this with reactive event streams?
Here's a rough idea of how I would expect to build it.
<div id="timeline">
<span id="scrubber"></span>
</div>
then, using Bacon.js to create event streams
var mousedowns = $('#timeline').asEventStream('mousedown');
var touchstarts = $('#timeline').asEventStream('touchstart');
var starts = Bacon.mergeAll(mousedowns, touchstarts);
var mousemoves = $('#timeline').asEventStream('mousemove');
var touchmoves = $('#timeline').asEventStream('touchmove');
var moves = Bacon.mergeAll(mousemoves, touchmoves);
var mouseups = $('#timeline').asEventStream('mouseup');
var touchends = $('#timeline').asEventStream('touchend');
var ends = Bacon.mergeAll(mouseups, touchends);
starts.onValue(function () {
var repositionScrubber = moves.onValue(function (ev) {
$('#scrubber').moveTo(ev.offsetX);
});
ends.onValue(function () {
repositionScrubber.stop();
});
});
I'm sure that's all sorts of wrong, but I'm really new to handling events with observable streams and I don't know of any good cookbooks for it yet. Any help will be appreciated!
The TouchEvent interface represents an event sent when the state of contacts with a touch-sensitive surface changes. The state changes are starting contact with a touch surface, moving a touch point while maintaining contact with the surface, releasing a touch point and canceling a touch event.
touches. Returns a list of all the touch objects that are currently in contact with the surface.
This is essentially the canonical drag and drop recipe.
The minimum working example in RxJS is something like this:
var $timeline = $('#timeline');
var $scrubber = $('#scrubber');
var mouseDown = Rx.Observable.merge(
Rx.Observable.fromEvent($timeline, 'mousedown'),
Rx.Observable.fromEvent($timeline, 'touchstart'));
var mouseUp = Rx.Observable.merge(
Rx.Observable.fromEvent($timeline, 'mouseup'),
Rx.Observable.fromEvent($timeline, 'touchend'));
var mouseMove = Rx.Observable.merge(
Rx.Observable.fromEvent($timeline, 'mousemove'),
Rx.Observable.fromEvent($timeline, 'touchmove'));
var subscription = mouseDown.flatMapLatest(function(md) {
// calculate offsets when mouse down
var startX = md.offsetX;
return mouseMove.takeUntil(mouseUp)
.map(function(mm) {
mm.preventDefault();
return {
left: mm.clientX - startX,
};
});
})
.subscribe(function(e) {
$scrubber.css(e);
});
var $timeline = $('#timeline');
var $scrubber = $('#scrubber');
var mouseDown = Rx.Observable.merge(
Rx.Observable.fromEvent($timeline, 'mousedown'),
Rx.Observable.fromEvent($timeline, 'touchstart'));
var mouseUp = Rx.Observable.merge(
Rx.Observable.fromEvent($timeline, 'mouseup'),
Rx.Observable.fromEvent($timeline, 'touchend'));
var mouseMove = Rx.Observable.merge(
Rx.Observable.fromEvent($timeline, 'mousemove'),
Rx.Observable.fromEvent($timeline, 'touchmove'));
var subscription = mouseDown.flatMapLatest(function(md) {
// calculate offsets when mouse down
var startX = md.offsetX;
return mouseMove.takeUntil(mouseUp)
.map(function(mm) {
mm.preventDefault();
return {
left: mm.clientX - startX,
};
});
})
.subscribe(function(e) {
$scrubber.css(e);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.3/rx.all.js"></script>
<div id="timeline" style="height: 100px; width: 100px; background: yellow; position: absolute;">
<span id="scrubber" style="height: 20px; width: 30px; background: green; position: relative;">Foo</span>
</div>
var mousemove = Rx.Observable.merge(
Rx.Observable.fromEvent(document, 'mousemove')
.map((e) => { e.preventDefault(); return e; }),
Rx.Observable.fromEvent(document, 'touchmove'))
.map((e) => e.touches[0]),
mouseup = Rx.Observable.merge(
Rx.Observable.fromEvent(dragTarget, 'mouseup'),
Rx.Observable.fromEvent(dragTarget, 'touchend')),
mousedown = Rx.Observable.merge(
Rx.Observable.fromEvent(dragTarget, 'mousedown'),
Rx.Observable.fromEvent(dragTarget, 'touchstart')
.map((e) => {
var rect = e.target.getBoundingClientRect();
e.offsetX = e.touches[0].pageX - rect.left;
e.offsetY = e.touches[0].pageY - rect.top;
return e;
}));
mousedown
.flatMap((start) => mousemove
.map((mm) =>({
left: mm.clientX - start.offsetX,
top: mm.clientY - start.offsetY
}))
.takeUntil(mouseup))
.subscribe(function (pos) {
dragTarget.style.top = pos.top + 'px';
dragTarget.style.left = pos.left + 'px';
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With