优化页面滚动事件
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import { scroller } from "./scroller";
|
||||
import { initTheme, setH5 } from "./theme";
|
||||
import { initLocale } from "@/locale";
|
||||
|
||||
export function cool(app: VueApp) {
|
||||
app.mixin({
|
||||
onPageScroll(e) {
|
||||
scroller.emit(e.scrollTop);
|
||||
},
|
||||
onShow() {
|
||||
// #ifdef H5
|
||||
setTimeout(() => {
|
||||
@@ -18,11 +22,12 @@ export function cool(app: VueApp) {
|
||||
console.log(app);
|
||||
}
|
||||
|
||||
export * from "./utils";
|
||||
export * from "./theme";
|
||||
export * from "./router";
|
||||
export * from "./service";
|
||||
export * from "./hooks";
|
||||
export * from "./ctx";
|
||||
export * from "./hooks";
|
||||
export * from "./router";
|
||||
export * from "./scroller";
|
||||
export * from "./service";
|
||||
export * from "./store";
|
||||
export * from "./theme";
|
||||
export * from "./upload";
|
||||
export * from "./utils";
|
||||
|
||||
23
cool/scroller/index.ts
Normal file
23
cool/scroller/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { router } from "../router";
|
||||
|
||||
class Scroller {
|
||||
list: Map<string, ((top: number) => void)[]> = new Map();
|
||||
|
||||
// 触发滚动
|
||||
emit(top: number) {
|
||||
const cbs = this.list.get(router.path()) ?? [];
|
||||
cbs.forEach((cb) => {
|
||||
cb(top);
|
||||
});
|
||||
}
|
||||
|
||||
// 监听页面滚动
|
||||
on(callback: (top: number) => void) {
|
||||
const path = router.path();
|
||||
const cbs = this.list.get(path) ?? [];
|
||||
cbs.push(callback);
|
||||
this.list.set(path, cbs);
|
||||
}
|
||||
}
|
||||
|
||||
export const scroller = new Scroller();
|
||||
@@ -17,6 +17,9 @@
|
||||
:pt="{
|
||||
indexBar: {
|
||||
className: '!fixed'
|
||||
},
|
||||
itemHover: {
|
||||
className: 'bg-gray-200'
|
||||
}
|
||||
}"
|
||||
>
|
||||
@@ -29,12 +32,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { request } from "@/cool";
|
||||
import DemoItem from "../components/item.uvue";
|
||||
import { useListView, type ClListViewItem } from "@/uni_modules/cool-ui";
|
||||
import { useListView, useUi, type ClListViewItem } from "@/uni_modules/cool-ui";
|
||||
import { ref } from "vue";
|
||||
|
||||
const ui = useUi();
|
||||
|
||||
const data = ref<ClListViewItem[]>([]);
|
||||
|
||||
onReady(() => {
|
||||
ui.showLoading();
|
||||
|
||||
request<UTSJSONObject[]>({
|
||||
url: "https://unix.cool-js.com/data/pca_flat.json"
|
||||
})
|
||||
@@ -43,6 +50,9 @@ onReady(() => {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
ui.hideLoading();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getTabBarHeight, hasCustomTabBar } from "@/cool";
|
||||
import { getTabBarHeight, hasCustomTabBar, scroller } from "@/cool";
|
||||
import { computed, onMounted, ref, watch, type PropType } from "vue";
|
||||
import { usePage } from "../../hooks";
|
||||
|
||||
@@ -27,10 +27,12 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["backTop"]);
|
||||
|
||||
const { screenHeight } = uni.getWindowInfo();
|
||||
|
||||
// cl-page 上下文
|
||||
const page = usePage();
|
||||
const { scrollToTop, onScroll } = usePage();
|
||||
|
||||
// 是否显示回到顶部按钮
|
||||
const visible = ref(false);
|
||||
@@ -46,6 +48,9 @@ const bottom = computed(() => {
|
||||
return h + "px";
|
||||
});
|
||||
|
||||
// 是否页面滚动
|
||||
const isPage = computed(() => props.top == null);
|
||||
|
||||
// 控制是否显示
|
||||
function onVisible(top: number) {
|
||||
visible.value = top > screenHeight - 100;
|
||||
@@ -53,11 +58,20 @@ function onVisible(top: number) {
|
||||
|
||||
// 回到顶部
|
||||
function toTop() {
|
||||
page.scrollToTop();
|
||||
if (isPage.value) {
|
||||
scrollToTop();
|
||||
}
|
||||
|
||||
emit("backTop");
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.top != null) {
|
||||
if (isPage.value) {
|
||||
// 监听页面滚动
|
||||
onScroll((top) => {
|
||||
onVisible(top);
|
||||
});
|
||||
} else {
|
||||
// 监听参数变化
|
||||
watch(
|
||||
computed(() => props.top!),
|
||||
@@ -68,22 +82,16 @@ onMounted(() => {
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 监听页面滚动
|
||||
page.onPageScroll((top) => {
|
||||
onVisible(top);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-back-top {
|
||||
@apply flex flex-row items-center justify-center bg-primary-500 rounded-full;
|
||||
@apply flex flex-row items-center justify-center bg-primary-500 rounded-full duration-300;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
transition-property: transform;
|
||||
transition-duration: 0.3s;
|
||||
transform: translateX(160rpx);
|
||||
|
||||
&.is-show {
|
||||
@@ -91,8 +99,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
@apply fixed z-50 overflow-visible;
|
||||
right: 0;
|
||||
@apply fixed right-0 z-50 overflow-visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
<template>
|
||||
<view class="cl-list-view" :class="[pt.className]">
|
||||
<cl-index-bar
|
||||
v-if="hasIndex"
|
||||
v-model="activeIndex"
|
||||
:list="indexList"
|
||||
:pt="{
|
||||
className: parseClass([pt.indexBar?.className])
|
||||
}"
|
||||
@change="onIndexChange"
|
||||
>
|
||||
</cl-index-bar>
|
||||
|
||||
<!-- 滚动容器 -->
|
||||
<scroll-view
|
||||
class="cl-list-view__scroller"
|
||||
:class="[pt.scroller?.className]"
|
||||
@@ -33,6 +23,7 @@
|
||||
@refresherrestore="onRefresherRestore"
|
||||
@refresherabort="onRefresherAbort"
|
||||
>
|
||||
<!-- 下拉刷新 -->
|
||||
<view
|
||||
slot="refresher"
|
||||
class="cl-list-view__refresher"
|
||||
@@ -59,15 +50,18 @@
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view
|
||||
class="cl-list-view__virtual-list"
|
||||
:class="[pt.list?.className]"
|
||||
:style="listStyle"
|
||||
>
|
||||
<!-- 顶部占位 -->
|
||||
<view class="cl-list-view__spacer-top" :style="spacerTopStyle">
|
||||
<slot name="top"></slot>
|
||||
</view>
|
||||
|
||||
<!-- 列表项 -->
|
||||
<view
|
||||
v-for="(item, index) in visibleItems"
|
||||
:key="item.key"
|
||||
@@ -99,6 +93,7 @@
|
||||
},
|
||||
pt.item?.className
|
||||
]"
|
||||
:hover-class="pt.itemHover?.className"
|
||||
:style="{
|
||||
height: virtual ? itemHeight + 'px' : 'auto'
|
||||
}"
|
||||
@@ -118,15 +113,29 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="cl-list-view__spacer-bottom" :style="spacerBottomStyle">
|
||||
<slot name="bottom"></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<cl-empty v-if="noData" :fixed="false"></cl-empty>
|
||||
<cl-back-top :top="scrollTop" v-if="showBackTop" @tap="scrollToTop"></cl-back-top>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右侧索引栏 -->
|
||||
<cl-index-bar
|
||||
v-if="hasIndex"
|
||||
v-model="activeIndex"
|
||||
:list="indexList"
|
||||
:pt="{
|
||||
className: parseClass([pt.indexBar?.className])
|
||||
}"
|
||||
@change="onIndexChange"
|
||||
>
|
||||
</cl-index-bar>
|
||||
|
||||
<!-- 索引提示 -->
|
||||
<view
|
||||
class="cl-list-view__index"
|
||||
:class="[
|
||||
@@ -141,6 +150,9 @@
|
||||
<cl-text> {{ indexList[activeIndex] }} </cl-text>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 回到顶部 -->
|
||||
<cl-back-top :top="scrollTop" v-if="showBackTop" @back-top="scrollToTop"></cl-back-top>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -295,6 +307,7 @@ const { proxy } = getCurrentInstance()!;
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
item?: PassThroughProps;
|
||||
itemHover?: PassThroughProps;
|
||||
list?: PassThroughProps;
|
||||
indexBar?: PassThroughProps;
|
||||
scroller?: PassThroughProps;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:scroll-with-animation="true"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<cl-back-top v-if="backTop" @tap="scrollToTop"></cl-back-top>
|
||||
<cl-back-top v-if="backTop"></cl-back-top>
|
||||
<theme></theme>
|
||||
<ui></ui>
|
||||
<slot></slot>
|
||||
@@ -14,7 +14,7 @@
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef APP -->
|
||||
<cl-back-top v-if="backTop" @tap="scrollToTop"></cl-back-top>
|
||||
<cl-back-top v-if="backTop"></cl-back-top>
|
||||
<theme></theme>
|
||||
<ui></ui>
|
||||
<slot></slot>
|
||||
@@ -27,7 +27,7 @@ import Theme from "./theme.uvue";
|
||||
import Ui from "./ui.uvue";
|
||||
import { locale, t } from "@/locale";
|
||||
import { config } from "@/config";
|
||||
import { router } from "@/cool";
|
||||
import { router, scroller } from "@/cool";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-page"
|
||||
@@ -49,12 +49,13 @@ const scrollViewTop = ref(0);
|
||||
|
||||
// view 滚动事件
|
||||
function onScroll(e: UniScrollEvent) {
|
||||
scrollTop.value = e.detail.scrollTop;
|
||||
// 触发滚动事件
|
||||
scroller.emit(e.detail.scrollTop);
|
||||
}
|
||||
|
||||
// 页面滚动事件
|
||||
onPageScroll((e) => {
|
||||
scrollTop.value = e.scrollTop;
|
||||
scroller.on((top) => {
|
||||
scrollTop.value = top;
|
||||
});
|
||||
|
||||
// 滚动到指定位置
|
||||
|
||||
@@ -57,9 +57,9 @@ const props = defineProps({
|
||||
const { proxy } = getCurrentInstance()!;
|
||||
|
||||
// cl-page 上下文
|
||||
const page = usePage();
|
||||
const { onScroll } = usePage();
|
||||
|
||||
// 定义Rect类型,表示元素的位置信息
|
||||
// 表示元素的位置信息
|
||||
type Rect = {
|
||||
height: number; // 高度
|
||||
width: number; // 宽度
|
||||
@@ -67,7 +67,7 @@ type Rect = {
|
||||
top: number; // 距离页面顶部的距离
|
||||
};
|
||||
|
||||
// rect为响应式对象,存储当前sticky元素的位置信息
|
||||
// 存储当前sticky元素的位置信息
|
||||
const rect = reactive<Rect>({
|
||||
height: 0,
|
||||
width: 0,
|
||||
@@ -75,7 +75,7 @@ const rect = reactive<Rect>({
|
||||
top: 0
|
||||
});
|
||||
|
||||
// scrollTop为当前页面滚动的距离
|
||||
// 当前页面滚动的距离
|
||||
const scrollTop = ref(0);
|
||||
|
||||
// 计算属性,判断当前是否处于吸顶状态
|
||||
@@ -128,14 +128,16 @@ function getRect() {
|
||||
});
|
||||
}
|
||||
|
||||
// 监听页面滚动事件
|
||||
page.onPageScroll((top) => {
|
||||
scrollTop.value = top;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 获取元素位置信息
|
||||
getRect();
|
||||
|
||||
// 监听页面滚动事件
|
||||
onScroll((top) => {
|
||||
scrollTop.value = top;
|
||||
});
|
||||
|
||||
// 监听参数变化
|
||||
watch(
|
||||
computed(() => props.scrollTop),
|
||||
(top: number) => {
|
||||
|
||||
@@ -1,66 +1,50 @@
|
||||
import { router, useParent } from "@/cool";
|
||||
import { computed, watch } from "vue";
|
||||
|
||||
type PageScrollCallback = (top: number) => void;
|
||||
import { router, scroller, useParent } from "@/cool";
|
||||
|
||||
class Page {
|
||||
listeners: PageScrollCallback[] = [];
|
||||
pageRef: ClPageComponentPublicInstance | null = null;
|
||||
|
||||
constructor() {
|
||||
this.pageRef = useParent<ClPageComponentPublicInstance>("cl-page");
|
||||
|
||||
if (this.pageRef != null) {
|
||||
// TODO: 小程序异常
|
||||
watch(
|
||||
computed(() => this.pageRef!.scrollTop),
|
||||
(top: number) => {
|
||||
this.listeners.forEach((listener) => {
|
||||
listener(top);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面路径
|
||||
* @returns 页面路径
|
||||
*/
|
||||
path() {
|
||||
path = () => {
|
||||
return router.path();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取滚动位置
|
||||
* @returns 滚动位置
|
||||
*/
|
||||
getScrollTop(): number {
|
||||
getScrollTop = (): number => {
|
||||
return this.pageRef!.scrollTop as number;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 滚动到指定位置
|
||||
* @param top 滚动位置
|
||||
*/
|
||||
scrollTo(top: number) {
|
||||
scrollTo = (top: number) => {
|
||||
this.pageRef!.scrollTo(top);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 回到顶部
|
||||
*/
|
||||
scrollToTop() {
|
||||
scrollToTop = () => {
|
||||
this.pageRef!.scrollToTop();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 监听页面滚动
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
onPageScroll(callback: PageScrollCallback) {
|
||||
this.listeners.push(callback);
|
||||
}
|
||||
onScroll = (callback: (top: number) => void) => {
|
||||
scroller.on(callback);
|
||||
};
|
||||
}
|
||||
|
||||
export function usePage(): Page {
|
||||
|
||||
Reference in New Issue
Block a user