|
| 1 | +<template> |
| 2 | + <div |
| 3 | + v-if="$q.platform.is.desktop" |
| 4 | + class="q-scrollarea scroll relative-position overflow-hidden" |
| 5 | + @mousewheel="__mouseWheel" |
| 6 | + @DOMMouseScroll="__mouseWheel" |
| 7 | + @mouseenter="hover = true" |
| 8 | + @mouseleave="hover = false" |
| 9 | + > |
| 10 | + <div class="absolute" v-touch-pan.vertical.nomouse="__panContainer"> |
| 11 | + <slot></slot> |
| 12 | + <q-resize-observable @resize="__updateScrollHeight"></q-resize-observable> |
| 13 | + <q-scroll-observable @scroll="__updateScroll"></q-scroll-observable> |
| 14 | + </div> |
| 15 | + |
| 16 | + <q-resize-observable @resize="__updateContainer"></q-resize-observable> |
| 17 | + <div |
| 18 | + class="q-scrollarea-thumb absolute-right" |
| 19 | + :style="thumbStyle" |
| 20 | + :class="{visible: thumbVisible}" |
| 21 | + v-touch-pan.vertical="__panThumb" |
| 22 | + ></div> |
| 23 | + </div> |
| 24 | + <div |
| 25 | + v-else |
| 26 | + class="scroll relative-position" |
| 27 | + > |
| 28 | + <slot></slot> |
| 29 | + </div> |
| 30 | +</template> |
| 31 | + |
| 32 | +<script> |
| 33 | +import { between } from '../../utils/format' |
| 34 | +import { getMouseWheelDistance } from '../../utils/event' |
| 35 | +import { QResizeObservable, QScrollObservable } from '../observables' |
| 36 | +import TouchPan from '../../directives/touch-pan' |
| 37 | +
|
| 38 | +export default { |
| 39 | + name: 'q-scroll-area', |
| 40 | + components: { |
| 41 | + QResizeObservable, |
| 42 | + QScrollObservable |
| 43 | + }, |
| 44 | + directives: { |
| 45 | + TouchPan |
| 46 | + }, |
| 47 | + props: { |
| 48 | + width: { |
| 49 | + type: String, |
| 50 | + default: '10px' |
| 51 | + }, |
| 52 | + delay: { |
| 53 | + type: Number, |
| 54 | + default: 1000 |
| 55 | + } |
| 56 | + }, |
| 57 | + data () { |
| 58 | + return { |
| 59 | + active: false, |
| 60 | + hover: false, |
| 61 | + containerHeight: 0, |
| 62 | + scrollPosition: 0, |
| 63 | + scrollHeight: 0 |
| 64 | + } |
| 65 | + }, |
| 66 | + computed: { |
| 67 | + thumbVisible () { |
| 68 | + return this.thumbHeight < this.scrollHeight && (this.active || this.hover) |
| 69 | + }, |
| 70 | + thumbHeight () { |
| 71 | + return Math.round(Math.max(20, this.containerHeight * this.containerHeight / this.scrollHeight)) |
| 72 | + }, |
| 73 | + thumbStyle () { |
| 74 | + const top = Math.min( |
| 75 | + this.scrollPosition + (this.scrollPercentage * (this.containerHeight - this.thumbHeight)), |
| 76 | + this.scrollHeight - this.thumbHeight |
| 77 | + ) |
| 78 | + return { |
| 79 | + top: `${top}px`, |
| 80 | + width: this.width, |
| 81 | + height: `${this.thumbHeight}px` |
| 82 | + } |
| 83 | + }, |
| 84 | + scrollPercentage () { |
| 85 | + const p = between(this.scrollPosition / (this.scrollHeight - this.containerHeight), 0, 1) |
| 86 | + return Math.round(p * 10000) / 10000 |
| 87 | + } |
| 88 | + }, |
| 89 | + methods: { |
| 90 | + __updateContainer (size) { |
| 91 | + if (this.containerHeight !== size.height) { |
| 92 | + this.containerHeight = size.height |
| 93 | + this.__setActive(true, true) |
| 94 | + } |
| 95 | + }, |
| 96 | + __updateScroll (scroll) { |
| 97 | + if (this.scrollPosition !== scroll.position) { |
| 98 | + this.scrollPosition = scroll.position |
| 99 | + this.__setActive(true, true) |
| 100 | + } |
| 101 | + }, |
| 102 | + __updateScrollHeight ({height}) { |
| 103 | + if (this.scrollHeight !== height) { |
| 104 | + this.scrollHeight = height |
| 105 | + this.__setActive(true, true) |
| 106 | + } |
| 107 | + }, |
| 108 | + __panThumb (e) { |
| 109 | + if (e.isFirst) { |
| 110 | + this.refPos = this.scrollPosition |
| 111 | + this.__setActive(true, true) |
| 112 | + } |
| 113 | + if (e.isFinal) { |
| 114 | + this.__setActive(false) |
| 115 | + } |
| 116 | +
|
| 117 | + const |
| 118 | + sign = (e.direction === 'down' ? 1 : -1), |
| 119 | + multiplier = (this.scrollHeight - this.containerHeight) / (this.containerHeight - this.thumbHeight) |
| 120 | +
|
| 121 | + this.$el.scrollTop = this.refPos + sign * e.distance.y * multiplier |
| 122 | + }, |
| 123 | + __panContainer (e) { |
| 124 | + if (e.isFirst) { |
| 125 | + this.refPos = this.scrollPosition |
| 126 | + this.__setActive(true, true) |
| 127 | + } |
| 128 | + if (e.isFinal) { |
| 129 | + this.__setActive(false) |
| 130 | + } |
| 131 | +
|
| 132 | + const el = this.$el |
| 133 | + el.scrollTop = this.refPos + (e.direction === 'down' ? -1 : 1) * e.distance.y |
| 134 | + if (el.scrollTop > 0 && el.scrollTop + this.containerHeight < this.scrollHeight) { |
| 135 | + e.evt.preventDefault() |
| 136 | + } |
| 137 | + }, |
| 138 | + __mouseWheel (e) { |
| 139 | + const el = this.$el |
| 140 | + el.scrollTop += getMouseWheelDistance(e).pixelY |
| 141 | + if (el.scrollTop > 0 && el.scrollTop + this.containerHeight < this.scrollHeight) { |
| 142 | + e.preventDefault() |
| 143 | + } |
| 144 | + }, |
| 145 | + __setActive (active, timer) { |
| 146 | + clearTimeout(this.timer) |
| 147 | + if (active === this.active) { |
| 148 | + if (active && this.timer) { |
| 149 | + this.__startTimer() |
| 150 | + } |
| 151 | + return |
| 152 | + } |
| 153 | +
|
| 154 | + if (active) { |
| 155 | + this.active = true |
| 156 | + if (timer) { |
| 157 | + this.__startTimer() |
| 158 | + } |
| 159 | + } |
| 160 | + else { |
| 161 | + this.active = false |
| 162 | + } |
| 163 | + }, |
| 164 | + __startTimer () { |
| 165 | + this.timer = setTimeout(() => { |
| 166 | + this.active = false |
| 167 | + this.timer = null |
| 168 | + }, this.delay) |
| 169 | + } |
| 170 | + } |
| 171 | +} |
| 172 | +</script> |
0 commit comments