OLD | NEW |
| (Empty) |
1 <!-- | |
2 // Copyright 2015 The Chromium Authors. All rights reserved. | |
3 // Use of this source code is governed by a BSD-style license that can be | |
4 // found in the LICENSE file. | |
5 --> | |
6 <import src="sky-element.sky" /> | |
7 | |
8 <sky-element> | |
9 <template> | |
10 <style> | |
11 :host { | |
12 overflow: hidden; | |
13 position: relative; | |
14 will-change: transform; | |
15 } | |
16 #scrollable { | |
17 will-change: transform; | |
18 } | |
19 #vbar { | |
20 position: absolute; | |
21 right: 0; | |
22 background-color: lightgray; | |
23 pointer-events: none; | |
24 top: 0; | |
25 height: 0; | |
26 will-change: opacity; | |
27 opacity: 0; | |
28 transition-property: opacity; | |
29 transition-function: ease-in-out; | |
30 } | |
31 </style> | |
32 <div id="scrollable"> | |
33 <content /> | |
34 </div> | |
35 <div id="vbar" /> | |
36 </template> | |
37 <script> | |
38 import "dart:math" as math; | |
39 import "dart:sky"; | |
40 import "animation/fling-curve.dart"; | |
41 import "theme/view-configuration.dart" as config; | |
42 | |
43 @Tagname('sky-scrollable') | |
44 class SkyScrollable extends SkyElement { | |
45 Element _scrollable; | |
46 Element _vbar; | |
47 double _scrollOffset = 0.0; | |
48 FlingCurve _flingCurve; | |
49 int _flingAnimationId; | |
50 | |
51 SkyScrollable() { | |
52 addEventListener('gesturescrollstart', _handleScrollStart); | |
53 addEventListener('gesturescrollend', _handleScrollEnd); | |
54 addEventListener('gesturescrollupdate', _handleScrollUpdate); | |
55 addEventListener('gestureflingstart', _handleFlingStart); | |
56 addEventListener('gestureflingcancel', _handleFlingCancel); | |
57 addEventListener('wheel', _handleWheel); | |
58 } | |
59 | |
60 void shadowRootReady() { | |
61 _scrollable = shadowRoot.getElementById('scrollable'); | |
62 _vbar = shadowRoot.getElementById('vbar'); | |
63 // This is not documented anywhere, but the scrollbar appears to only paint | |
64 // 3px even though it's official width is 10px? | |
65 // Chrome appears 3px wide with a 3px outer spacing. | |
66 // Contacts appears 3px wide with a 5px runner and 5px outer spacing. | |
67 // Settings appears 4px wide with no outer spacing. | |
68 const double paintPercent = 0.3; | |
69 const double outerGapPercent = 0.3; | |
70 const double innerGapPercent = 0.4; | |
71 const double paintWidth = paintPercent * config.kScrollbarSize; | |
72 _vbar.style['width'] = "${paintWidth}px"; | |
73 _vbar.style['margin-right'] = "${outerGapPercent * config.kScrollbarSize}px"
; | |
74 _vbar.style['margin-left'] ="${innerGapPercent * config.kScrollbarSize}px"; | |
75 // The scroll thumb never quite makes it to the top or bottom in gmail | |
76 // or chrome (in chrome more from the bottom than the top). | |
77 _vbar.style['margin-top'] = "${config.kScrollbarSize}px"; | |
78 _vbar.style['margin-bottom'] = "${config.kScrollbarSize}px"; | |
79 | |
80 // Some android apps round their scrollbars, some don't, not rounding for no
w. | |
81 | |
82 const double msToSeconds = 1.0 / 1000.0; | |
83 _vbar.style['transition-duration'] = "${msToSeconds * config.kScrollbarFadeD
uration}s"; | |
84 _vbar.style['transition-delay'] = "${msToSeconds * config.kScrollbarFadeDela
y}s"; | |
85 } | |
86 | |
87 double get scrollOffset => _scrollOffset; | |
88 | |
89 set scrollOffset(double value) { | |
90 // TODO(abarth): Can we get these values without forcing a synchronous layou
t? | |
91 double outerHeight = clientHeight.toDouble(); | |
92 double innerHeight = _scrollable.clientHeight.toDouble(); | |
93 double scrollRange = innerHeight - outerHeight; | |
94 double newScrollOffset = math.max(0.0, math.min(scrollRange, value)); | |
95 if (newScrollOffset == _scrollOffset) | |
96 return; | |
97 // TODO(eseidel): We should scroll in device pixels instead of logical | |
98 // pixels, but to do that correctly we need to use a device pixel unit. | |
99 _scrollOffset = newScrollOffset; | |
100 String transform = 'translateY(${(-_scrollOffset).toInt()}px)'; | |
101 _scrollable.style['transform'] = transform; | |
102 | |
103 double topPercent = newScrollOffset / innerHeight * 100.0; | |
104 double heightPercent = outerHeight / innerHeight * 100.0; | |
105 _vbar.style['top'] = '${topPercent}%'; | |
106 _vbar.style['height'] = '${heightPercent}%'; | |
107 } | |
108 | |
109 bool scrollBy(double scrollDelta) { | |
110 double oldScrollOffset = _scrollOffset; | |
111 scrollOffset += scrollDelta; | |
112 return _scrollOffset != oldScrollOffset; | |
113 } | |
114 | |
115 void _scheduleFlingUpdate() { | |
116 _flingAnimationId = window.requestAnimationFrame(_updateFling); | |
117 } | |
118 | |
119 void _stopFling() { | |
120 window.cancelAnimationFrame(_flingAnimationId); | |
121 _flingCurve = null; | |
122 _flingAnimationId = null; | |
123 _vbar.style['opacity'] = '0'; | |
124 } | |
125 | |
126 void _updateFling(double timeStamp) { | |
127 double scrollDelta = _flingCurve.update(timeStamp); | |
128 if (scrollDelta == 0.0 || !scrollBy(scrollDelta)) | |
129 _stopFling(); | |
130 else | |
131 _scheduleFlingUpdate(); | |
132 } | |
133 | |
134 void _handleScrollStart(_) { | |
135 _vbar.style['opacity'] = '1'; | |
136 } | |
137 | |
138 void _handleScrollEnd(_) { | |
139 _vbar.style['opacity'] = '0'; | |
140 } | |
141 | |
142 void _handleScrollUpdate(GestureEvent event) { | |
143 scrollBy(-event.dy); | |
144 } | |
145 | |
146 void _handleFlingStart(GestureEvent event) { | |
147 _flingCurve = new FlingCurve(-event.velocityY, event.timeStamp); | |
148 _scheduleFlingUpdate(); | |
149 } | |
150 | |
151 void _handleFlingCancel(_) { | |
152 _stopFling(); | |
153 } | |
154 | |
155 void _handleWheel(WheelEvent event) { | |
156 scrollBy(-event.offsetY); | |
157 } | |
158 } | |
159 | |
160 _init(script) => register(script, SkyScrollable); | |
161 </script> | |
162 </sky-element> | |
OLD | NEW |