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