提交 cae888e8 编写于 作者: D DCloud_LXH

refactor: scroll-view

上级 58ddc008
......@@ -19,7 +19,7 @@ import Radio from './radio/index'
import RadioGroup from './radio-group/index'
import ResizeSensor from './resize-sensor/index'
import RichText from './rich-text'
import ScrollView from './scroll-view/index.vue'
import ScrollView from './scroll-view/index'
import Slider from './slider/index'
import Swiper from './swiper/index'
import SwiperItem from './swiper-item/index'
......
import {
Ref,
ref,
ExtractPropTypes,
computed,
reactive,
onMounted,
onBeforeUnmount,
onActivated,
watch,
} from 'vue'
import { passive } from '@dcloudio/uni-shared'
import { initScrollBounce, disableScrollBounce } from '../../helpers/scroll'
import {
useCustomEvent,
CustomEventTrigger,
EmitEvent,
} from '../../helpers/useEvent'
import { defineBuiltInComponent } from '@dcloudio/uni-components'
type HTMLRef = Ref<HTMLElement | null>
type Props = ExtractPropTypes<typeof props>
type RefreshState = 'refreshing' | 'restore' | 'pulling' | ''
interface State {
lastScrollTop: number
lastScrollLeft: number
lastScrollToUpperTime: number
lastScrollToLowerTime: number
refresherHeight: number
refreshRotate: number
refreshState: RefreshState
}
const passiveOptions = passive(true)
const props = {
scrollX: {
type: [Boolean, String],
default: false,
},
scrollY: {
type: [Boolean, String],
default: false,
},
upperThreshold: {
type: [Number, String],
default: 50,
},
lowerThreshold: {
type: [Number, String],
default: 50,
},
scrollTop: {
type: [Number, String],
default: 0,
},
scrollLeft: {
type: [Number, String],
default: 0,
},
scrollIntoView: {
type: String,
default: '',
},
scrollWithAnimation: {
type: [Boolean, String],
default: false,
},
enableBackToTop: {
type: [Boolean, String],
default: false,
},
refresherEnabled: {
type: [Boolean, String],
default: false,
},
refresherThreshold: {
type: Number,
default: 45,
},
refresherDefaultStyle: {
type: String,
default: 'back',
},
refresherBackground: {
type: String,
default: '#fff',
},
refresherTriggered: {
type: [Boolean, String],
default: false,
},
}
export default /*#__PURE__*/ defineBuiltInComponent({
name: 'ScrollView',
compatConfig: {
MODE: 3,
},
props,
emits: ['scroll', 'scrolltoupper', 'scrolltolower', 'refresherabort'],
setup(props, { emit, slots }) {
const rootRef: HTMLRef = ref(null)
const main: HTMLRef = ref(null)
const wrap: HTMLRef = ref(null)
const content: HTMLRef = ref(null)
const refresherinner: HTMLRef = ref(null)
const trigger = useCustomEvent<EmitEvent<typeof emit>>(rootRef, emit)
const { state, scrollTopNumber, scrollLeftNumber } =
useScrollViewState(props)
useScrollViewLoader(
props,
state,
scrollTopNumber,
scrollLeftNumber,
trigger,
rootRef,
main,
content
)
return () => {
const {
scrollX,
refresherEnabled,
refresherBackground,
refresherDefaultStyle,
} = props
const { refresherHeight, refreshState, refreshRotate } = state
return (
<uni-scroll-view ref={rootRef}>
<div ref={wrap} class="uni-scroll-view">
<div
ref={main}
style={{
overflowX: scrollX ? 'auto' : 'hidden',
overflowY: scrollY ? 'auto' : 'hidden',
}}
class="uni-scroll-view"
>
<div ref={content} class="uni-scroll-view-content">
{refresherEnabled ? (
<div
ref={refresherinner}
style={{
backgroundColor: refresherBackground,
height: refresherHeight + 'px',
}}
class="uni-scroll-view-refresher"
>
{refresherDefaultStyle !== 'none' ? (
<div class="uni-scroll-view-refresh">
<div class="uni-scroll-view-refresh-inner">
{refreshState == 'pulling' ? (
<svg
style={{
transform: 'rotate(' + refreshRotate + 'deg)',
}}
fill="#2BD009"
class="uni-scroll-view-refresh__icon"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
) : null}
{refreshState == 'refreshing' ? (
<svg
class="uni-scroll-view-refresh__spinner"
width="24"
height="24"
viewBox="25 25 50 50"
>
<circle
cx="50"
cy="50"
r="20"
fill="none"
style="color: #2bd009"
stroke-width="3"
/>
</svg>
) : null}
</div>
</div>
) : null}
{refresherDefaultStyle == 'none'
? slots.refresher && slots.refresher()
: null}
</div>
) : null}
{slots.default && slots.default()}
</div>
</div>
</div>
</uni-scroll-view>
)
}
},
})
function useScrollViewState(props: Props) {
const scrollTopNumber = computed(() => {
return Number(props.scrollTop) || 0
})
const scrollLeftNumber = computed(() => {
return Number(props.scrollLeft) || 0
})
const state: State = reactive({
lastScrollTop: scrollTopNumber.value,
lastScrollLeft: scrollLeftNumber.value,
lastScrollToUpperTime: 0,
lastScrollToLowerTime: 0,
refresherHeight: 0,
refreshRotate: 0,
refreshState: '',
})
return {
state,
scrollTopNumber,
scrollLeftNumber,
}
}
function useScrollViewLoader(
props: Props,
state: State,
scrollTopNumber: Ref<number>,
scrollLeftNumber: Ref<number>,
trigger: CustomEventTrigger,
rootRef: HTMLRef,
main: HTMLRef,
content: HTMLRef
) {
let _lastScrollTime = 0
let _innerSetScrollTop = false
let _innerSetScrollLeft = false
let __transitionEnd = () => {}
const upperThresholdNumber = computed(() => {
var val = Number(props.upperThreshold)
return isNaN(val) ? 50 : val
})
const lowerThresholdNumber = computed(() => {
var val = Number(props.lowerThreshold)
return isNaN(val) ? 50 : val
})
function scrollTo(t: number, n: 'x' | 'y') {
var i = main.value!
t < 0
? (t = 0)
: n === 'x' && t > i.scrollWidth - i.offsetWidth
? (t = i.scrollWidth - i.offsetWidth)
: n === 'y' &&
t > i.scrollHeight - i.offsetHeight &&
(t = i.scrollHeight - i.offsetHeight)
var r = 0
var o = ''
n === 'x' ? (r = i.scrollLeft - t) : n === 'y' && (r = i.scrollTop - t)
if (r !== 0) {
content.value!.style.transition = 'transform .3s ease-out'
content.value!.style.webkitTransition = '-webkit-transform .3s ease-out'
if (n === 'x') {
o = 'translateX(' + r + 'px) translateZ(0)'
} else {
n === 'y' && (o = 'translateY(' + r + 'px) translateZ(0)')
}
content.value!.removeEventListener('transitionend', __transitionEnd)
content.value!.removeEventListener('webkitTransitionEnd', __transitionEnd)
__transitionEnd = () => _transitionEnd(t, n)
content.value!.addEventListener('transitionend', __transitionEnd)
content.value!.addEventListener('webkitTransitionEnd', __transitionEnd)
if (n === 'x') {
// if (e !== 'ios') {
i.style.overflowX = 'hidden'
// }
} else if (n === 'y') {
i.style.overflowY = 'hidden'
}
content.value!.style.transform = o
content.value!.style.webkitTransform = o
}
}
function _handleScroll($event: MouseEvent) {
if ($event.timeStamp - _lastScrollTime > 20) {
_lastScrollTime = $event.timeStamp
const target = $event.target as HTMLElement
trigger('scroll', $event, {
scrollLeft: target.scrollLeft,
scrollTop: target.scrollTop,
scrollHeight: target.scrollHeight,
scrollWidth: target.scrollWidth,
deltaX: state.lastScrollLeft - target.scrollLeft,
deltaY: state.lastScrollTop - target.scrollTop,
})
if (props.scrollY) {
if (
target.scrollTop <= upperThresholdNumber.value &&
state.lastScrollTop - target.scrollTop > 0 &&
$event.timeStamp - state.lastScrollToUpperTime > 200
) {
trigger('scrolltoupper', $event, {
direction: 'top',
})
state.lastScrollToUpperTime = $event.timeStamp
}
if (
target.scrollTop + target.offsetHeight + lowerThresholdNumber.value >=
target.scrollHeight &&
state.lastScrollTop - target.scrollTop < 0 &&
$event.timeStamp - state.lastScrollToLowerTime > 200
) {
trigger('scrolltolower', $event, {
direction: 'bottom',
})
state.lastScrollToLowerTime = $event.timeStamp
}
}
if (props.scrollX) {
if (
target.scrollLeft <= upperThresholdNumber.value &&
state.lastScrollLeft - target.scrollLeft > 0 &&
$event.timeStamp - state.lastScrollToUpperTime > 200
) {
trigger('scrolltoupper', $event, {
direction: 'left',
})
state.lastScrollToUpperTime = $event.timeStamp
}
if (
target.scrollLeft + target.offsetWidth + lowerThresholdNumber.value >=
target.scrollWidth &&
state.lastScrollLeft - target.scrollLeft < 0 &&
$event.timeStamp - state.lastScrollToLowerTime > 200
) {
trigger('scrolltolower', $event, {
direction: 'right',
})
state.lastScrollToLowerTime = $event.timeStamp
}
}
state.lastScrollTop = target.scrollTop
state.lastScrollLeft = target.scrollLeft
}
}
function _scrollTopChanged(val: number) {
if (props.scrollY) {
if (_innerSetScrollTop) {
_innerSetScrollTop = false
} else {
if (props.scrollWithAnimation) {
scrollTo(val, 'y')
} else {
main.value!.scrollTop = val
}
}
}
}
function _scrollLeftChanged(val: number) {
if (props.scrollX) {
if (_innerSetScrollLeft) {
_innerSetScrollLeft = false
} else {
if (props.scrollWithAnimation) {
scrollTo(val, 'x')
} else {
main.value!.scrollLeft = val
}
}
}
}
function _scrollIntoViewChanged(val: string) {
if (val) {
if (!/^[_a-zA-Z][-_a-zA-Z0-9:]*$/.test(val)) {
console.error(`id error: scroll-into-view=${val}`)
return
}
var element = rootRef.value!.querySelector('#' + val)
if (element) {
var mainRect = main.value!.getBoundingClientRect()
var elRect = element.getBoundingClientRect()
if (props.scrollX) {
var left = elRect.left - mainRect.left
var scrollLeft = main.value!.scrollLeft
var x = scrollLeft + left
if (props.scrollWithAnimation) {
scrollTo(x, 'x')
} else {
main.value!.scrollLeft = x
}
}
if (props.scrollY) {
var top = elRect.top - mainRect.top
var scrollTop = main.value!.scrollTop
var y = scrollTop + top
if (props.scrollWithAnimation) {
scrollTo(y, 'y')
} else {
main.value!.scrollTop = y
}
}
}
}
}
function _transitionEnd(val: number, type: 'x' | 'y') {
content.value!.style.transition = ''
content.value!.style.webkitTransition = ''
content.value!.style.transform = ''
content.value!.style.webkitTransform = ''
let _main = main.value!
if (type === 'x') {
_main.style.overflowX = props.scrollX ? 'auto' : 'hidden'
_main.scrollLeft = val
} else if (type === 'y') {
_main.style.overflowY = props.scrollY ? 'auto' : 'hidden'
_main.scrollTop = val
}
content.value!.removeEventListener('transitionend', __transitionEnd)
content.value!.removeEventListener('webkitTransitionEnd', __transitionEnd)
}
function _setRefreshState(_state: RefreshState) {
switch (_state) {
case 'refreshing':
state.refresherHeight = props.refresherThreshold
trigger('refresherrefresh', {} as Event, {})
break
case 'restore':
state.refresherHeight = 0
trigger('refresherrestore', {} as Event, {})
break
}
state.refreshState = _state
}
/* function getScrollPosition() {
const _main = main.value!
return {
scrollLeft: _main.scrollLeft,
scrollTop: _main.scrollTop,
scrollHeight: _main.scrollHeight,
scrollWidth: _main.scrollWidth,
}
} */
onMounted(() => {
_scrollTopChanged(scrollTopNumber.value)
_scrollLeftChanged(scrollLeftNumber.value)
_scrollIntoViewChanged(props.scrollIntoView)
let __handleScroll = function (event: Event) {
// Unable to preventDefault inside passive event listener invocation.
// event.preventDefault();
event.stopPropagation()
_handleScroll(event as MouseEvent)
}
let touchStart: {
x: number
y: number
} = {
x: 0,
y: 0,
}
let needStop: boolean | null = null
let __handleTouchMove = function (_event: Event) {
const event = _event as TouchEvent
var x = event.touches[0].pageX
var y = event.touches[0].pageY
var _main = main.value!
if (needStop === null) {
if (Math.abs(x - touchStart.x) > Math.abs(y - touchStart.y)) {
// 横向滑动
if (self.scrollX) {
if (_main.scrollLeft === 0 && x > touchStart.x) {
needStop = false
return
} else if (
_main.scrollWidth === _main.offsetWidth + _main.scrollLeft &&
x < touchStart.x
) {
needStop = false
return
}
needStop = true
} else {
needStop = false
}
} else {
// 纵向滑动
if (self.scrollY) {
if (_main.scrollTop === 0 && y > touchStart.y) {
needStop = false
return
} else if (
_main.scrollHeight === _main.offsetHeight + _main.scrollTop &&
y < touchStart.y
) {
needStop = false
return
}
needStop = true
} else {
needStop = false
}
}
}
if (needStop) {
event.stopPropagation()
}
if (props.refresherEnabled && state.refreshState === 'pulling') {
const dy = y - touchStart.y
state.refresherHeight = dy
let rotate = dy / props.refresherThreshold
if (rotate > 1) {
rotate = 1
} else {
rotate = rotate * 360
}
state.refreshRotate = rotate
trigger('refresherpulling', event, {
deltaY: dy,
})
}
}
let __handleTouchStart = function (_event: Event) {
const event = _event as TouchEvent
if (event.touches.length === 1) {
disableScrollBounce({
disable: true,
})
needStop = null
touchStart = {
x: event.touches[0].pageX,
y: event.touches[0].pageY,
}
if (
props.refresherEnabled &&
state.refreshState !== 'refreshing' &&
main.value!.scrollTop === 0
) {
state.refreshState = 'pulling'
}
}
}
let __handleTouchEnd = function (_event: Event) {
const event = _event as TouchEvent
touchStart = {
x: 0,
y: 0,
}
disableScrollBounce({
disable: false,
})
if (state.refresherHeight >= props.refresherThreshold) {
_setRefreshState('refreshing')
} else {
state.refresherHeight = 0
trigger('refresherabort', event, {})
}
}
main.value!.addEventListener(
'touchstart',
__handleTouchStart,
passiveOptions
)
main.value!.addEventListener('touchmove', __handleTouchMove, passiveOptions)
main.value!.addEventListener('scroll', __handleScroll, passiveOptions)
main.value!.addEventListener('touchend', __handleTouchEnd, passiveOptions)
initScrollBounce()
onBeforeUnmount(() => {
main.value!.removeEventListener('touchstart', __handleTouchStart)
main.value!.removeEventListener('touchmove', __handleTouchMove)
main.value!.removeEventListener('scroll', __handleScroll)
main.value!.removeEventListener('touchend', __handleTouchEnd)
})
})
onActivated(() => {
// 还原 scroll-view 滚动位置
props.scrollY && (main.value!.scrollTop = state.lastScrollTop)
props.scrollX && (main.value!.scrollLeft = state.lastScrollLeft)
})
watch(scrollTopNumber, (val) => {
_scrollTopChanged(val)
})
watch(scrollLeftNumber, (val) => {
_scrollLeftChanged(val)
})
watch(
() => props.scrollIntoView,
(val) => {
_scrollIntoViewChanged(val)
}
)
watch(
() => props.refresherTriggered,
(val) => {
// TODO
if (val === true) {
_setRefreshState('refreshing')
} else if (val === false) {
_setRefreshState('restore')
}
}
)
}
<template>
<uni-scroll-view ref="rootRef">
<div ref="wrap" class="uni-scroll-view">
<div
ref="main"
:style="{
'overflow-x': scrollX ? 'auto' : 'hidden',
'overflow-y': scrollY ? 'auto' : 'hidden',
}"
class="uni-scroll-view"
>
<div ref="content" class="uni-scroll-view-content">
<div
v-if="refresherEnabled"
ref="refresherinner"
:style="{
'background-color': refresherBackground,
height: refresherHeight + 'px',
}"
class="uni-scroll-view-refresher"
>
<div v-if="refresherDefaultStyle !== 'none'" class="uni-scroll-view-refresh">
<div class="uni-scroll-view-refresh-inner">
<svg
v-if="refreshState == 'pulling'"
:style="{ transform: 'rotate(' + refreshRotate + 'deg)' }"
fill="#2BD009"
class="uni-scroll-view-refresh__icon"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
/>
<path d="M0 0h24v24H0z" fill="none" />
</svg>
<svg
v-if="refreshState == 'refreshing'"
class="uni-scroll-view-refresh__spinner"
width="24"
height="24"
viewBox="25 25 50 50"
>
<circle
cx="50"
cy="50"
r="20"
fill="none"
style="color: #2bd009"
stroke-width="3"
/>
</svg>
</div>
</div>
<slot v-if="refresherDefaultStyle == 'none'" name="refresher" />
</div>
<slot />
</div>
</div>
</div>
</uni-scroll-view>
</template>
<script>
import { ref } from "vue";
import { passive } from "@dcloudio/uni-shared";
import { initScrollBounce, disableScrollBounce } from "../../helpers/scroll";
import { useCustomEvent } from "../../helpers/useEvent";
const passiveOptions = passive(true);
// const PULLING = 'pulling'
// const REFRESHING = 'refreshing'
export default {
name: "ScrollView",
compatConfig: {
MODE: 3
},
props: {
scrollX: {
type: [Boolean, String],
default: false,
},
scrollY: {
type: [Boolean, String],
default: false,
},
upperThreshold: {
type: [Number, String],
default: 50,
},
lowerThreshold: {
type: [Number, String],
default: 50,
},
scrollTop: {
type: [Number, String],
default: 0,
},
scrollLeft: {
type: [Number, String],
default: 0,
},
scrollIntoView: {
type: String,
default: "",
},
scrollWithAnimation: {
type: [Boolean, String],
default: false,
},
enableBackToTop: {
type: [Boolean, String],
default: false,
},
refresherEnabled: {
type: [Boolean, String],
default: false,
},
refresherThreshold: {
type: Number,
default: 45,
},
refresherDefaultStyle: {
type: String,
default: "back",
},
refresherBackground: {
type: String,
default: "#fff",
},
refresherTriggered: {
type: [Boolean, String],
default: false,
},
},
data() {
return {
lastScrollTop: this.scrollTopNumber,
lastScrollLeft: this.scrollLeftNumber,
lastScrollToUpperTime: 0,
lastScrollToLowerTime: 0,
refresherHeight: 0,
refreshRotate: 0,
refreshState: "",
};
},
computed: {
upperThresholdNumber() {
var val = Number(this.upperThreshold);
return isNaN(val) ? 50 : val;
},
lowerThresholdNumber() {
var val = Number(this.lowerThreshold);
return isNaN(val) ? 50 : val;
},
scrollTopNumber() {
return Number(this.scrollTop) || 0;
},
scrollLeftNumber() {
return Number(this.scrollLeft) || 0;
},
},
watch: {
scrollTopNumber(val) {
this._scrollTopChanged(val);
},
scrollLeftNumber(val) {
this._scrollLeftChanged(val);
},
scrollIntoView(val) {
this._scrollIntoViewChanged(val);
},
refresherTriggered(val) {
// TODO
if (val === true) {
this._setRefreshState("refreshing");
} else if (val === false) {
this._setRefreshState("restore");
}
},
},
mounted() {
this.$trigger = useCustomEvent(
{
value: this.rootRef,
},
this.$emit
);
var self = this;
this._attached = true;
this._scrollTopChanged(this.scrollTopNumber);
this._scrollLeftChanged(this.scrollLeftNumber);
this._scrollIntoViewChanged(this.scrollIntoView);
this.__handleScroll = function (event) {
// Unable to preventDefault inside passive event listener invocation.
// event.preventDefault();
event.stopPropagation();
self._handleScroll.bind(self, event)();
};
var touchStart = null;
var needStop = null;
this.__handleTouchMove = function (event) {
var x = event.touches[0].pageX;
var y = event.touches[0].pageY;
var main = self.main;
if (needStop === null) {
if (Math.abs(x - touchStart.x) > Math.abs(y - touchStart.y)) {
// 横向滑动
if (self.scrollX) {
if (main.scrollLeft === 0 && x > touchStart.x) {
needStop = false;
return;
} else if (
main.scrollWidth === main.offsetWidth + main.scrollLeft &&
x < touchStart.x
) {
needStop = false;
return;
}
needStop = true;
} else {
needStop = false;
}
} else {
// 纵向滑动
if (self.scrollY) {
if (main.scrollTop === 0 && y > touchStart.y) {
needStop = false;
return;
} else if (
main.scrollHeight === main.offsetHeight + main.scrollTop &&
y < touchStart.y
) {
needStop = false;
return;
}
needStop = true;
} else {
needStop = false;
}
}
}
if (needStop) {
event.stopPropagation();
}
if (self.refresherEnabled && self.refreshState === "pulling") {
const dy = y - touchStart.y;
self.refresherHeight = dy;
let rotate = dy / self.refresherThreshold;
if (rotate > 1) {
rotate = 1;
} else {
rotate = rotate * 360;
}
self.refreshRotate = rotate;
self.$trigger("refresherpulling", event, {
deltaY: dy,
});
}
};
this.__handleTouchStart = function (event) {
if (event.touches.length === 1) {
disableScrollBounce({
disable: true,
});
needStop = null;
touchStart = {
x: event.touches[0].pageX,
y: event.touches[0].pageY,
};
if (
self.refresherEnabled &&
self.refreshState !== "refreshing" &&
self.main.scrollTop === 0
) {
self.refreshState = "pulling";
}
}
};
this.__handleTouchEnd = function (event) {
touchStart = null;
disableScrollBounce({
disable: false,
});
if (self.refresherHeight >= self.refresherThreshold) {
self._setRefreshState("refreshing");
} else {
self.refresherHeight = 0;
self.$trigger("refresherabort", event, {});
}
};
this.main.addEventListener("touchstart", this.__handleTouchStart, passiveOptions);
this.main.addEventListener("touchmove", this.__handleTouchMove, passiveOptions);
this.main.addEventListener("scroll", this.__handleScroll, passiveOptions);
this.main.addEventListener("touchend", this.__handleTouchEnd, passiveOptions);
initScrollBounce();
},
activated() {
// 还原 scroll-view 滚动位置
this.scrollY && (this.main.scrollTop = this.lastScrollTop);
this.scrollX && (this.main.scrollLeft = this.lastScrollLeft);
},
beforeUnmount() {
this.main.removeEventListener("touchstart", this.__handleTouchStart, passiveOptions);
this.main.removeEventListener("touchmove", this.__handleTouchMove, passiveOptions);
this.main.removeEventListener("scroll", this.__handleScroll, passiveOptions);
this.main.removeEventListener("touchend", this.__handleTouchEnd, passiveOptions);
},
methods: {
scrollTo: function (t, n) {
var i = this.main;
t < 0
? (t = 0)
: n === "x" && t > i.scrollWidth - i.offsetWidth
? (t = i.scrollWidth - i.offsetWidth)
: n === "y" &&
t > i.scrollHeight - i.offsetHeight &&
(t = i.scrollHeight - i.offsetHeight);
var r = 0;
var o = "";
n === "x" ? (r = i.scrollLeft - t) : n === "y" && (r = i.scrollTop - t);
if (r !== 0) {
this.content.style.transition = "transform .3s ease-out";
this.content.style.webkitTransition = "-webkit-transform .3s ease-out";
if (n === "x") {
o = "translateX(" + r + "px) translateZ(0)";
} else {
n === "y" && (o = "translateY(" + r + "px) translateZ(0)");
}
this.content.removeEventListener("transitionend", this.__transitionEnd);
this.content.removeEventListener("webkitTransitionEnd", this.__transitionEnd);
this.__transitionEnd = this._transitionEnd.bind(this, t, n);
this.content.addEventListener("transitionend", this.__transitionEnd);
this.content.addEventListener("webkitTransitionEnd", this.__transitionEnd);
if (n === "x") {
// if (e !== 'ios') {
i.style.overflowX = "hidden";
// }
} else if (n === "y") {
i.style.overflowY = "hidden";
}
this.content.style.transform = o;
this.content.style.webkitTransform = o;
}
},
_handleScroll: function ($event) {
if (!($event.timeStamp - this._lastScrollTime < 20)) {
this._lastScrollTime = $event.timeStamp;
const target = $event.target;
this.$trigger("scroll", $event, {
scrollLeft: target.scrollLeft,
scrollTop: target.scrollTop,
scrollHeight: target.scrollHeight,
scrollWidth: target.scrollWidth,
deltaX: this.lastScrollLeft - target.scrollLeft,
deltaY: this.lastScrollTop - target.scrollTop,
});
if (this.scrollY) {
if (
target.scrollTop <= this.upperThresholdNumber &&
this.lastScrollTop - target.scrollTop > 0 &&
$event.timeStamp - this.lastScrollToUpperTime > 200
) {
this.$trigger("scrolltoupper", $event, {
direction: "top",
});
this.lastScrollToUpperTime = $event.timeStamp;
}
if (
target.scrollTop + target.offsetHeight + this.lowerThresholdNumber >=
target.scrollHeight &&
this.lastScrollTop - target.scrollTop < 0 &&
$event.timeStamp - this.lastScrollToLowerTime > 200
) {
this.$trigger("scrolltolower", $event, {
direction: "bottom",
});
this.lastScrollToLowerTime = $event.timeStamp;
}
}
if (this.scrollX) {
if (
target.scrollLeft <= this.upperThresholdNumber &&
this.lastScrollLeft - target.scrollLeft > 0 &&
$event.timeStamp - this.lastScrollToUpperTime > 200
) {
this.$trigger("scrolltoupper", $event, {
direction: "left",
});
this.lastScrollToUpperTime = $event.timeStamp;
}
if (
target.scrollLeft + target.offsetWidth + this.lowerThresholdNumber >=
target.scrollWidth &&
this.lastScrollLeft - target.scrollLeft < 0 &&
$event.timeStamp - this.lastScrollToLowerTime > 200
) {
this.$trigger("scrolltolower", $event, {
direction: "right",
});
this.lastScrollToLowerTime = $event.timeStamp;
}
}
this.lastScrollTop = target.scrollTop;
this.lastScrollLeft = target.scrollLeft;
}
},
_scrollTopChanged: function (val) {
if (this.scrollY) {
if (this._innerSetScrollTop) {
this._innerSetScrollTop = false;
} else {
if (this.scrollWithAnimation) {
this.scrollTo(val, "y");
} else {
this.main.scrollTop = val;
}
}
}
},
_scrollLeftChanged: function (val) {
if (this.scrollX) {
if (this._innerSetScrollLeft) {
this._innerSetScrollLeft = false;
} else {
if (this.scrollWithAnimation) {
this.scrollTo(val, "x");
} else {
this.main.scrollLeft = val;
}
}
}
},
_scrollIntoViewChanged: function (val) {
if (val) {
if (!/^[_a-zA-Z][-_a-zA-Z0-9:]*$/.test(val)) {
console.error(`id error: scroll-into-view=${val}`);
return;
}
var element = this.rootRef.querySelector("#" + val);
if (element) {
var mainRect = this.main.getBoundingClientRect();
var elRect = element.getBoundingClientRect();
if (this.scrollX) {
var left = elRect.left - mainRect.left;
var scrollLeft = this.main.scrollLeft;
var x = scrollLeft + left;
if (this.scrollWithAnimation) {
this.scrollTo(x, "x");
} else {
this.main.scrollLeft = x;
}
}
if (this.scrollY) {
var top = elRect.top - mainRect.top;
var scrollTop = this.main.scrollTop;
var y = scrollTop + top;
if (this.scrollWithAnimation) {
this.scrollTo(y, "y");
} else {
this.main.scrollTop = y;
}
}
}
}
},
_transitionEnd: function (val, type) {
this.content.style.transition = "";
this.content.style.webkitTransition = "";
this.content.style.transform = "";
this.content.style.webkitTransform = "";
var main = this.main;
if (type === "x") {
main.style.overflowX = this.scrollX ? "auto" : "hidden";
main.scrollLeft = val;
} else if (type === "y") {
main.style.overflowY = this.scrollY ? "auto" : "hidden";
main.scrollTop = val;
}
this.content.removeEventListener("transitionend", this.__transitionEnd);
this.content.removeEventListener("webkitTransitionEnd", this.__transitionEnd);
},
_setRefreshState(state) {
switch (state) {
case "refreshing":
this.refresherHeight = this.refresherThreshold;
this.$trigger("refresherrefresh", {}, {});
break;
case "restore":
this.refresherHeight = 0;
this.$trigger("refresherrestore", {}, {});
break;
}
this.refreshState = state;
},
getScrollPosition() {
const main = this.main;
return {
scrollLeft: main.scrollLeft,
scrollTop: main.scrollTop,
scrollHeight: main.scrollHeight,
scrollWidth: main.scrollWidth,
};
},
},
setup(props) {
const rootRef = ref(null);
const main = ref(null);
const content = ref(null);
return {
rootRef,
main,
content,
};
},
};
</script>
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册