Skip to content

Commit 4dcdcab

Browse files
authored
feat(animated scroll): calculate animated scroll based on timers in requestAnimatedFrame quasarframework#7800 (quasarframework#8100)
1 parent a200c3a commit 4dcdcab

File tree

2 files changed

+152
-8
lines changed

2 files changed

+152
-8
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<template>
2+
<div ref="scrollableEl" class="absolute-full scroll text-dark">
3+
<div class="animation-scroll-test" />
4+
5+
<div class="fixed-top-left q-ma-lg q-pa-sm rounded-borders column" style="background: rgba(200, 200, 200, .4)">
6+
<q-input
7+
type="number"
8+
v-model="duration"
9+
:step="1"
10+
:min="100"
11+
:max="10000"
12+
label="Duration (ms)"
13+
outlined
14+
dense
15+
:dark="false"
16+
input-class="text-right"
17+
style="width: 7em"
18+
/>
19+
<q-btn unelevated class="q-mt-md" label="Scroll" @click="scroll" />
20+
</div>
21+
22+
<div class="fixed-bottom-left q-ma-lg q-pa-sm rounded-borders column text-no-wrap" style="width: 320px; background: rgba(200, 200, 200, .4)">
23+
<div class="row no-wrap">
24+
<div class="col-2">&nbsp;</div>
25+
<div class="col-2 text-right text-weight-medium">Left</div>
26+
<div class="col-3 text-right text-weight-medium">Time X</div>
27+
<div class="col-2 text-right text-weight-medium">Top</div>
28+
<div class="col-3 text-right text-weight-medium">Time Y</div>
29+
</div>
30+
<div class="row no-wrap">
31+
<div class="col-2 text-weight-medium">From</div>
32+
<div class="col-2 text-grey-8 text-right">{{from.left}}</div>
33+
<div class="col-3 text-grey-8 text-right">{{from.timeX.toFixed(2).slice(-8)}}</div>
34+
<div class="col-2 text-grey-8 text-right">{{from.top}}</div>
35+
<div class="col-3 text-grey-8 text-right">{{from.timeY.toFixed(2).slice(-8)}}</div>
36+
</div>
37+
<div class="row no-wrap">
38+
<div class="col-2 text-weight-medium">To</div>
39+
<div class="col-2 text-grey-8 text-right">{{to.left}}</div>
40+
<div class="col-3 text-grey-8 text-right">{{to.timeX.toFixed(2).slice(-8)}}</div>
41+
<div class="col-2 text-grey-8 text-right">{{to.top}}</div>
42+
<div class="col-3 text-grey-8 text-right">{{to.timeY.toFixed(2).slice(-8)}}</div>
43+
</div>
44+
<div class="row no-wrap">
45+
<div class="col-2 text-weight-medium">Diff</div>
46+
<div class="col-2 text-weight-medium text-right">{{diff.left}}</div>
47+
<div class="col-3 text-grey-8 text-right">{{diff.timeX.toFixed(2)}}</div>
48+
<div class="col-2 text-weight-medium text-right">{{diff.top}}</div>
49+
<div class="col-3 text-grey-8 text-right">{{diff.timeY.toFixed(2)}}</div>
50+
</div>
51+
<div class="row no-wrap">
52+
<div class="col-2 text-weight-medium">Dev</div>
53+
<div class="col-2">&nbsp;</div>
54+
<div class="col-3 text-right text-weight-medium">{{diff.devX.toFixed(2)}}</div>
55+
<div class="col-2">&nbsp;</div>
56+
<div class="col-3 text-right text-weight-medium">{{diff.devY.toFixed(2)}}</div>
57+
</div>
58+
</div>
59+
</div>
60+
</template>
61+
62+
<script>
63+
import { scroll } from 'quasar'
64+
65+
const { animScrollTo, animHorizontalScrollTo } = scroll
66+
67+
export default {
68+
data () {
69+
return {
70+
duration: 2000,
71+
from: { left: 0, top: 0, timeX: 0, timeY: 0, duration: 2000 },
72+
to: { left: 0, top: 0, timeX: 0, timeY: 0 }
73+
}
74+
},
75+
76+
computed: {
77+
diff () {
78+
return {
79+
left: this.to.left - this.from.left,
80+
top: this.to.top - this.from.top,
81+
timeX: this.to.timeX - this.from.timeX,
82+
timeY: this.to.timeY - this.from.timeY,
83+
devX: (this.to.timeX - this.from.timeX - this.from.duration) / this.from.duration * 100,
84+
devY: (this.to.timeY - this.from.timeY - this.from.duration) / this.from.duration * 100
85+
}
86+
}
87+
},
88+
89+
methods: {
90+
scroll () {
91+
const el = this.$refs.scrollableEl
92+
const timeStart = performance.now()
93+
94+
this.from = {
95+
left: Math.round(el.scrollLeft),
96+
top: Math.round(el.scrollTop),
97+
timeX: timeStart,
98+
timeY: timeStart,
99+
duration: this.duration
100+
}
101+
102+
this.to = {
103+
left: Math.round((el.scrollWidth - el.clientWidth) * Math.random()),
104+
top: Math.round((el.scrollHeight - el.clientHeight) * Math.random()),
105+
timeX: timeStart,
106+
timeY: timeStart
107+
}
108+
109+
let { left, top } = this.from
110+
const fn = e => {
111+
const time = performance.now()
112+
113+
if (el.scrollLeft !== left) {
114+
this.to.timeX = time
115+
left = el.scrollLeft
116+
}
117+
if (el.scrollTop !== top) {
118+
this.to.timeY = time
119+
top = el.scrollTop
120+
}
121+
}
122+
el.addEventListener('scroll', fn, { passive: true })
123+
setTimeout(() => {
124+
el.removeEventListener('scroll', fn, { passive: true })
125+
}, this.duration + 500)
126+
127+
animHorizontalScrollTo(el, this.to.left, this.duration)
128+
animScrollTo(el, this.to.top, this.duration)
129+
}
130+
}
131+
}
132+
</script>
133+
134+
<style lang="sass">
135+
.animation-scroll-test
136+
height: 10000px
137+
width: 10000px
138+
background: linear-gradient(-90deg, rgba(0,0,0,.05) 1px, transparent 1px), linear-gradient(rgba(0,0,0,.05) 1px, transparent 1px), linear-gradient(-90deg, rgba(0, 0, 0, .04) 1px, transparent 1px), linear-gradient(rgba(0,0,0,.04) 1px, transparent 1px), linear-gradient(transparent 3px, #f2f2f2 3px, #f2f2f2 78px, transparent 78px), linear-gradient(-90deg, #aaa 1px, transparent 1px), linear-gradient(-90deg, transparent 3px, #f2f2f2 3px, #f2f2f2 78px, transparent 78px), linear-gradient(#aaa 1px, transparent 1px), #f2f2f2 #{"/* rtl:ignore */"}
139+
background-size: 4px 4px, 4px 4px, 80px 80px, 80px 80px, 80px 80px, 80px 80px, 80px 80px, 80px 80px #{"/* rtl:ignore */"}
140+
</style>

ui/src/utils/scroll.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export function getHorizontalScrollPosition (scrollTarget) {
4949
return scrollTarget.scrollLeft
5050
}
5151

52-
export function animScrollTo (el, to, duration = 0) {
52+
export function animScrollTo (el, to, duration = 0 /* , prevTime */) {
53+
const prevTime = arguments[3] === void 0 ? performance.now() : arguments[3]
5354
const pos = getScrollPosition(el)
5455

5556
if (duration <= 0) {
@@ -59,16 +60,18 @@ export function animScrollTo (el, to, duration = 0) {
5960
return
6061
}
6162

62-
requestAnimationFrame(() => {
63-
const newPos = pos + (to - pos) / Math.max(16, duration) * 16
63+
requestAnimationFrame(nowTime => {
64+
const frameTime = nowTime - prevTime
65+
const newPos = pos + (to - pos) / Math.max(frameTime, duration) * frameTime
6466
setScroll(el, newPos)
6567
if (newPos !== to) {
66-
animScrollTo(el, to, duration - 16)
68+
animScrollTo(el, to, duration - frameTime, nowTime)
6769
}
6870
})
6971
}
7072

71-
export function animHorizontalScrollTo (el, to, duration = 0) {
73+
export function animHorizontalScrollTo (el, to, duration = 0 /* , prevTime */) {
74+
const prevTime = arguments[3] === void 0 ? performance.now() : arguments[3]
7275
const pos = getHorizontalScrollPosition(el)
7376

7477
if (duration <= 0) {
@@ -78,11 +81,12 @@ export function animHorizontalScrollTo (el, to, duration = 0) {
7881
return
7982
}
8083

81-
requestAnimationFrame(() => {
82-
const newPos = pos + (to - pos) / Math.max(16, duration) * 16
84+
requestAnimationFrame(nowTime => {
85+
const frameTime = nowTime - prevTime
86+
const newPos = pos + (to - pos) / Math.max(frameTime, duration) * frameTime
8387
setHorizontalScroll(el, newPos)
8488
if (newPos !== to) {
85-
animHorizontalScrollTo(el, to, duration - 16)
89+
animHorizontalScrollTo(el, to, duration - frameTime, nowTime)
8690
}
8791
})
8892
}

0 commit comments

Comments
 (0)