blob: 3e41d8838ab3e01369b5fc9d5607e18dcd18ea3c [file] [log] [blame]
<!--
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-->
<import src="sky-element.sky" />
<sky-element>
<template>
<style>
:host {
overflow: hidden;
position: relative;
will-change: transform;
}
#scrollable {
will-change: transform;
}
#vbar {
position: absolute;
right: 0;
width: 3px;
background-color: lightgray;
pointer-events: none;
top: 0;
height: 0;
will-change: opacity;
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
</style>
<div id="scrollable">
<content />
</div>
<div id="vbar" />
</template>
<script>
import "dart:math" as math;
import "dart:sky";
import "fling-curve.dart";
@Tagname('sky-scrollable')
class SkyScrollable extends SkyElement {
Element _scrollable;
Element _vbar;
double _scrollOffset = 0.0;
FlingCurve _flingCurve;
int _flingAnimationId;
SkyScrollable() {
addEventListener('gesturescrollstart', _handleScrollStart);
addEventListener('gesturescrollend', _handleScrollEnd);
addEventListener('gesturescrollupdate', _handleScrollUpdate);
addEventListener('gestureflingstart', _handleFlingStart);
addEventListener('gestureflingcancel', _handleFlingCancel);
addEventListener('wheel', _handleWheel);
}
void shadowRootReady() {
_scrollable = shadowRoot.getElementById('scrollable');
_vbar = shadowRoot.getElementById('vbar');
}
double get scrollOffset => _scrollOffset;
set scrollOffset(double value) {
// TODO(abarth): Can we get these values without forcing a synchronous layout?
double outerHeight = clientHeight.toDouble();
double innerHeight = _scrollable.clientHeight.toDouble();
double scrollRange = innerHeight - outerHeight;
double newScrollOffset = math.max(0.0, math.min(scrollRange, value));
if (newScrollOffset == _scrollOffset)
return;
_scrollOffset = newScrollOffset;
String transform = 'translateY(${(-_scrollOffset).toStringAsFixed(2)}px)';
_scrollable.style['transform'] = transform;
double topPercent = newScrollOffset / innerHeight * 100.0;
double heightPercent = outerHeight / innerHeight * 100.0;
_vbar.style['top'] = '${topPercent}%';
_vbar.style['height'] = '${heightPercent}%';
}
bool scrollBy(double scrollDelta) {
double oldScrollOffset = _scrollOffset;
scrollOffset += scrollDelta;
return _scrollOffset != oldScrollOffset;
}
void _scheduleFlingUpdate() {
_flingAnimationId = window.requestAnimationFrame(_updateFling);
}
void _stopFling() {
window.cancelAnimationFrame(_flingAnimationId);
_flingCurve = null;
_flingAnimationId = null;
_vbar.style['opacity'] = '0';
}
void _updateFling(double timeStamp) {
double scrollDelta = _flingCurve.update(timeStamp);
if (scrollDelta == 0.0 || !scrollBy(scrollDelta))
_stopFling();
else
_scheduleFlingUpdate();
}
void _handleScrollStart(_) {
_vbar.style['opacity'] = '1';
}
void _handleScrollEnd(_) {
_vbar.style['opacity'] = '0';
}
void _handleScrollUpdate(GestureEvent event) {
scrollBy(-event.dy);
}
void _handleFlingStart(GestureEvent event) {
_flingCurve = new FlingCurve(-event.velocityY, event.timeStamp);
_scheduleFlingUpdate();
}
void _handleFlingCancel(_) {
_stopFling();
}
void _handleWheel(WheelEvent event) {
scrollBy(-event.offsetY);
}
}
_init(script) => register(script, SkyScrollable);
</script>
</sky-element>