| 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 |