OLD | NEW |
1 <!-- | 1 <!-- |
2 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 3 // Use of this source code is governed by a BSD-style license that can be |
4 // found in the LICENSE file. | 4 // found in the LICENSE file. |
5 --> | 5 --> |
6 <import src="sky-element/sky-element.sky" as="SkyElement" /> | 6 <import src="sky-element.sky" /> |
7 <import src="fling-curve.sky" as="FlingCurve" /> | |
8 | 7 |
9 <sky-element | 8 <sky-element> |
10 name="sky-scrollable" | |
11 on-gesturescrollstart="handleScrollStart_" | |
12 on-gesturescrollend="handleScrollEnd_" | |
13 on-gesturescrollupdate="handleScrollUpdate_" | |
14 on-gestureflingstart="handleFlingStart_" | |
15 on-gestureflingcancel="handleFlingCancel_" | |
16 on-wheel="handleWheel_"> | |
17 <template> | 9 <template> |
18 <style> | 10 <style> |
19 :host { | 11 :host { |
20 overflow: hidden; | 12 overflow: hidden; |
21 position: relative; | 13 position: relative; |
22 } | 14 } |
23 #scrollable { | 15 #scrollable { |
24 transform: translateY(0); | 16 transform: translateY(0); |
25 } | 17 } |
26 #vbar { | 18 #vbar { |
27 position: absolute; | 19 position: absolute; |
28 right: 0; | 20 right: 0; |
29 width: 3px; | 21 width: 3px; |
30 background-color: lightgray; | 22 background-color: lightgray; |
31 pointer-events: none; | 23 pointer-events: none; |
32 top: 0; | 24 top: 0; |
33 height: 0; | 25 height: 0; |
34 will-change: opacity; | 26 will-change: opacity; |
35 opacity: 0; | 27 opacity: 0; |
36 transition: opacity 0.2s ease-in-out; | 28 transition: opacity 0.2s ease-in-out; |
37 } | 29 } |
38 </style> | 30 </style> |
39 <div id="scrollable"> | 31 <div id="scrollable"> |
40 <content /> | 32 <content /> |
41 </div> | 33 </div> |
42 <div id="vbar" /> | 34 <div id="vbar" /> |
43 </template> | 35 </template> |
44 <script> | 36 <script> |
45 module.exports = class extends SkyElement { | 37 import "dart:math" as math; |
46 created() { | 38 import "dart:sky"; |
47 this.scrollable_ = null; | 39 import "fling-curve.dart"; |
48 this.vbar_ = null; | 40 |
49 this.scrollOffset_ = 0; | 41 @Tagname('sky-scrollable') |
50 this.flingCurve_ = null; | 42 class SkyScrollable extends SkyElement { |
51 this.flingAnimationId_ = null; | 43 Element _scrollable; |
| 44 Element _vbar; |
| 45 double _scrollOffset = 0.0; |
| 46 FlingCurve _flingCurve; |
| 47 int _flingAnimationId; |
| 48 |
| 49 SkyScrollable() { |
| 50 addEventListener('gesturescrollstart', _handleScrollStart); |
| 51 addEventListener('gesturescrollend', _handleScrollEnd); |
| 52 addEventListener('gesturescrollupdate', _handleScrollUpdate); |
| 53 addEventListener('gestureflingstart', _handleFlingStart); |
| 54 addEventListener('gestureflingcancel', _handleFlingCancel); |
| 55 addEventListener('wheel', _handleWheel); |
52 } | 56 } |
53 | 57 |
54 shadowRootReady() { | 58 void shadowRootReady() { |
55 this.scrollable_ = this.shadowRoot.getElementById('scrollable'); | 59 _scrollable = shadowRoot.getElementById('scrollable'); |
56 this.vbar_ = this.shadowRoot.getElementById('vbar'); | 60 _vbar = shadowRoot.getElementById('vbar'); |
57 } | 61 } |
58 | 62 |
59 get scrollOffset() { | 63 double get scrollOffset => _scrollOffset; |
60 return this.scrollOffset_; | 64 |
| 65 set scrollOffset(double value) { |
| 66 // TODO(abarth): Can we get these values without forcing a synchronous layou
t? |
| 67 double outerHeight = clientHeight.toDouble(); |
| 68 double innerHeight = _scrollable.clientHeight.toDouble(); |
| 69 double scrollRange = innerHeight - outerHeight; |
| 70 double newScrollOffset = math.max(0.0, math.min(scrollRange, value)); |
| 71 if (newScrollOffset == _scrollOffset) |
| 72 return; |
| 73 _scrollOffset = newScrollOffset; |
| 74 String transform = 'translateY(${(-_scrollOffset).toStringAsFixed(2)}px)'; |
| 75 _scrollable.style['transform'] = transform; |
| 76 |
| 77 double topPercent = newScrollOffset / innerHeight * 100.0; |
| 78 double heightPercent = outerHeight / innerHeight * 100.0; |
| 79 _vbar.style['top'] = '${topPercent}%'; |
| 80 _vbar.style['height'] = '${heightPercent}%'; |
61 } | 81 } |
62 | 82 |
63 set scrollOffset(value) { | 83 bool scrollBy(double scrollDelta) { |
64 // TODO(abarth): Can we get these values without forcing a synchronous layou
t? | 84 double oldScrollOffset = _scrollOffset; |
65 var outerHeight = this.clientHeight; | 85 scrollOffset += scrollDelta; |
66 var innerHeight = this.scrollable_.clientHeight; | 86 return _scrollOffset != oldScrollOffset; |
67 var scrollRange = innerHeight - outerHeight; | |
68 var newScrollOffset = Math.max(0, Math.min(scrollRange, value)); | |
69 if (newScrollOffset == this.scrollOffset_) | |
70 return; | |
71 this.scrollOffset_ = newScrollOffset; | |
72 var transform = 'translateY(' + -this.scrollOffset_.toFixed(2) + 'px)'; | |
73 this.scrollable_.style.transform = transform; | |
74 | |
75 var topPercent = newScrollOffset / innerHeight * 100; | |
76 var heightPercent = outerHeight / innerHeight * 100; | |
77 this.vbar_.style.top = topPercent + '%'; | |
78 this.vbar_.style.height = heightPercent + '%'; | |
79 } | 87 } |
80 | 88 |
81 scrollBy(scrollDelta) { | 89 void _scheduleFlingUpdate() { |
82 var oldScrollOffset = this.scrollOffset_; | 90 _flingAnimationId = window.requestAnimationFrame(_updateFling); |
83 this.scrollOffset += scrollDelta; | |
84 return this.scrollOffset_ != oldScrollOffset; | |
85 } | 91 } |
86 | 92 |
87 scheduleFlingUpdate_() { | 93 void _stopFling() { |
88 this.flingAnimationId_ = requestAnimationFrame(this.updateFling_.bind(this))
; | 94 window.cancelAnimationFrame(_flingAnimationId); |
| 95 _flingCurve = null; |
| 96 _flingAnimationId = null; |
| 97 _vbar.style['opacity'] = '0'; |
89 } | 98 } |
90 | 99 |
91 stopFling_() { | 100 void _updateFling(double timeStamp) { |
92 cancelAnimationFrame(this.flingAnimationId_); | 101 double scrollDelta = _flingCurve.update(timeStamp); |
93 this.flingCurve_ = null; | 102 if (scrollDelta == 0.0 || !scrollBy(scrollDelta)) |
94 this.flingAnimationId_ = null; | 103 _stopFling(); |
95 this.vbar_.style.opacity = 0; | 104 else |
| 105 _scheduleFlingUpdate(); |
96 } | 106 } |
97 | 107 |
98 updateFling_(timeStamp) { | 108 void _handleScrollStart(_) { |
99 var scrollDelta = this.flingCurve_.update(timeStamp); | 109 _vbar.style['opacity'] = '1'; |
100 if (!scrollDelta || !this.scrollBy(scrollDelta)) | |
101 return this.stopFling_(); | |
102 this.scheduleFlingUpdate_(); | |
103 } | 110 } |
104 | 111 |
105 handleScrollStart_(event) { | 112 void _handleScrollEnd(_) { |
106 this.vbar_.style.opacity = 1; | 113 _vbar.style['opacity'] = '0'; |
107 } | 114 } |
108 | 115 |
109 handleScrollEnd_(event) { | 116 void _handleScrollUpdate(GestureEvent event) { |
110 this.vbar_.style.opacity = 0; | 117 scrollBy(-event.dy); |
111 } | 118 } |
112 | 119 |
113 handleScrollUpdate_(event) { | 120 void _handleFlingStart(GestureEvent event) { |
114 this.scrollBy(-event.dy); | 121 _flingCurve = new FlingCurve(-event.velocityY, event.timeStamp); |
| 122 _scheduleFlingUpdate(); |
115 } | 123 } |
116 | 124 |
117 handleFlingStart_(event) { | 125 void _handleFlingCancel(_) { |
118 this.flingCurve_ = new FlingCurve(-event.velocityY, event.timeStamp); | 126 _stopFling(); |
119 this.scheduleFlingUpdate_(); | |
120 } | 127 } |
121 | 128 |
122 handleFlingCancel_(event) { | 129 void _handleWheel(WheelEvent event) { |
123 this.stopFling_(); | 130 scrollBy(-event.offsetY); |
124 } | 131 } |
| 132 } |
125 | 133 |
126 handleWheel_(event) { | 134 _init(script) => register(script, SkyScrollable); |
127 this.scrollBy(-event.offsetY); | |
128 } | |
129 }.register(); | |
130 </script> | 135 </script> |
131 </sky-element> | 136 </sky-element> |
OLD | NEW |