11 changed files with 2 additions and 382 deletions
@ -1,10 +1,8 @@ |
|||||
import { withInstall } from '/@/utils'; |
import { withInstall } from '/@/utils'; |
||||
import collapseContainer from './src/collapse/CollapseContainer.vue'; |
import collapseContainer from './src/collapse/CollapseContainer.vue'; |
||||
import scrollContainer from './src/ScrollContainer.vue'; |
import scrollContainer from './src/ScrollContainer.vue'; |
||||
import lazyContainer from './src/LazyContainer.vue'; |
|
||||
|
|
||||
export const CollapseContainer = withInstall(collapseContainer); |
export const CollapseContainer = withInstall(collapseContainer); |
||||
export const ScrollContainer = withInstall(scrollContainer); |
export const ScrollContainer = withInstall(scrollContainer); |
||||
export const LazyContainer = withInstall(lazyContainer); |
|
||||
|
|
||||
export * from './src/typing'; |
export * from './src/typing'; |
||||
|
|||||
@ -1,145 +0,0 @@ |
|||||
<template> |
|
||||
<transition-group |
|
||||
class="h-full w-full" |
|
||||
v-bind="$attrs" |
|
||||
ref="elRef" |
|
||||
:name="transitionName" |
|
||||
:tag="tag" |
|
||||
mode="out-in" |
|
||||
> |
|
||||
<div key="component" v-if="isInit"> |
|
||||
<slot :loading="loading"></slot> |
|
||||
</div> |
|
||||
<div key="skeleton" v-else> |
|
||||
<slot name="skeleton" v-if="$slots.skeleton"></slot> |
|
||||
<Skeleton v-else /> |
|
||||
</div> |
|
||||
</transition-group> |
|
||||
</template> |
|
||||
<script lang="ts"> |
|
||||
import type { PropType } from 'vue'; |
|
||||
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'; |
|
||||
import { useTimeoutFn } from '@vben/hooks'; |
|
||||
import { Skeleton } from 'ant-design-vue'; |
|
||||
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'; |
|
||||
|
|
||||
interface State { |
|
||||
isInit: boolean; |
|
||||
loading: boolean; |
|
||||
intersectionObserverInstance: IntersectionObserver | null; |
|
||||
} |
|
||||
|
|
||||
const props = { |
|
||||
/** |
|
||||
* Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time |
|
||||
*/ |
|
||||
timeout: { type: Number }, |
|
||||
/** |
|
||||
* The viewport where the component is located. |
|
||||
* If the component is scrolling in the page container, the viewport is the container |
|
||||
*/ |
|
||||
viewport: { |
|
||||
type: (typeof window !== 'undefined' ? window.HTMLElement : Object) as PropType<HTMLElement>, |
|
||||
default: () => null, |
|
||||
}, |
|
||||
/** |
|
||||
* Preload threshold, css unit |
|
||||
*/ |
|
||||
threshold: { type: String, default: '0px' }, |
|
||||
/** |
|
||||
* The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction |
|
||||
*/ |
|
||||
direction: { |
|
||||
type: String, |
|
||||
default: 'vertical', |
|
||||
validator: (v) => ['vertical', 'horizontal'].includes(v), |
|
||||
}, |
|
||||
/** |
|
||||
* The label name of the outer container that wraps the component |
|
||||
*/ |
|
||||
tag: { type: String, default: 'div' }, |
|
||||
maxWaitingTime: { type: Number, default: 80 }, |
|
||||
/** |
|
||||
* transition name |
|
||||
*/ |
|
||||
transitionName: { type: String, default: 'lazy-container' }, |
|
||||
}; |
|
||||
|
|
||||
export default defineComponent({ |
|
||||
name: 'LazyContainer', |
|
||||
components: { Skeleton }, |
|
||||
inheritAttrs: false, |
|
||||
props, |
|
||||
emits: ['init'], |
|
||||
setup(props, { emit }) { |
|
||||
const elRef = ref(); |
|
||||
const state = reactive<State>({ |
|
||||
isInit: false, |
|
||||
loading: false, |
|
||||
intersectionObserverInstance: null, |
|
||||
}); |
|
||||
|
|
||||
onMounted(() => { |
|
||||
immediateInit(); |
|
||||
initIntersectionObserver(); |
|
||||
}); |
|
||||
|
|
||||
// If there is a set delay time, it will be executed immediately |
|
||||
function immediateInit() { |
|
||||
const { timeout } = props; |
|
||||
timeout && |
|
||||
useTimeoutFn(() => { |
|
||||
init(); |
|
||||
}, timeout); |
|
||||
} |
|
||||
|
|
||||
function init() { |
|
||||
state.loading = true; |
|
||||
|
|
||||
useTimeoutFn(() => { |
|
||||
if (state.isInit) return; |
|
||||
state.isInit = true; |
|
||||
emit('init'); |
|
||||
}, props.maxWaitingTime || 80); |
|
||||
} |
|
||||
|
|
||||
function initIntersectionObserver() { |
|
||||
const { timeout, direction, threshold } = props; |
|
||||
if (timeout) return; |
|
||||
// According to the scrolling direction to construct the viewport margin, used to load in advance |
|
||||
let rootMargin = '0px'; |
|
||||
switch (direction) { |
|
||||
case 'vertical': |
|
||||
rootMargin = `${threshold} 0px`; |
|
||||
break; |
|
||||
case 'horizontal': |
|
||||
rootMargin = `0px ${threshold}`; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
const { stop, observer } = useIntersectionObserver({ |
|
||||
rootMargin, |
|
||||
target: toRef(elRef.value, '$el'), |
|
||||
onIntersect: (entries: any[]) => { |
|
||||
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio; |
|
||||
if (isIntersecting) { |
|
||||
init(); |
|
||||
if (observer) { |
|
||||
stop(); |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
root: toRef(props, 'viewport'), |
|
||||
}); |
|
||||
} catch (e) { |
|
||||
init(); |
|
||||
} |
|
||||
} |
|
||||
return { |
|
||||
elRef, |
|
||||
...toRefs(state), |
|
||||
}; |
|
||||
}, |
|
||||
}); |
|
||||
</script> |
|
||||
@ -1,48 +0,0 @@ |
|||||
import { Ref, watchEffect, ref } from 'vue'; |
|
||||
|
|
||||
interface IntersectionObserverProps { |
|
||||
target: Ref<Element | null | undefined>; |
|
||||
root?: Ref<any>; |
|
||||
onIntersect: IntersectionObserverCallback; |
|
||||
rootMargin?: string; |
|
||||
threshold?: number; |
|
||||
} |
|
||||
|
|
||||
export function useIntersectionObserver({ |
|
||||
target, |
|
||||
root, |
|
||||
onIntersect, |
|
||||
rootMargin = '0px', |
|
||||
threshold = 0.1, |
|
||||
}: IntersectionObserverProps) { |
|
||||
let cleanup = () => {}; |
|
||||
const observer: Ref<Nullable<IntersectionObserver>> = ref(null); |
|
||||
const stopEffect = watchEffect(() => { |
|
||||
cleanup(); |
|
||||
|
|
||||
observer.value = new IntersectionObserver(onIntersect, { |
|
||||
root: root ? root.value : null, |
|
||||
rootMargin, |
|
||||
threshold, |
|
||||
}); |
|
||||
|
|
||||
const current = target.value; |
|
||||
|
|
||||
current && observer.value.observe(current); |
|
||||
|
|
||||
cleanup = () => { |
|
||||
if (observer.value) { |
|
||||
observer.value.disconnect(); |
|
||||
target.value && observer.value.unobserve(target.value); |
|
||||
} |
|
||||
}; |
|
||||
}); |
|
||||
|
|
||||
return { |
|
||||
observer, |
|
||||
stop: () => { |
|
||||
cleanup(); |
|
||||
stopEffect(); |
|
||||
}, |
|
||||
}; |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
<template> |
|
||||
<Card hoverable :style="{ width: '240px', background: '#fff' }"> |
|
||||
<template #cover> |
|
||||
<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" /> |
|
||||
</template> |
|
||||
<CardMeta title="懒加载组件" /> |
|
||||
</Card> |
|
||||
</template> |
|
||||
<script lang="ts"> |
|
||||
import { defineComponent } from 'vue'; |
|
||||
import { Card } from 'ant-design-vue'; |
|
||||
|
|
||||
export default defineComponent({ |
|
||||
components: { CardMeta: Card.Meta, Card }, |
|
||||
setup() { |
|
||||
return {}; |
|
||||
}, |
|
||||
}); |
|
||||
</script> |
|
||||
@ -1,77 +0,0 @@ |
|||||
<template> |
|
||||
<PageWrapper title="懒加载自定义动画示例" content="懒加载组件显示动画"> |
|
||||
<div class="lazy-base-demo-wrap"> |
|
||||
<h1>向下滚动</h1> |
|
||||
|
|
||||
<div class="lazy-base-demo-box"> |
|
||||
<LazyContainer transitionName="custom"> |
|
||||
<TargetContent /> |
|
||||
</LazyContainer> |
|
||||
</div> |
|
||||
</div> |
|
||||
</PageWrapper> |
|
||||
</template> |
|
||||
<script lang="ts"> |
|
||||
import { defineComponent } from 'vue'; |
|
||||
import TargetContent from './TargetContent.vue'; |
|
||||
import { LazyContainer } from '/@/components/Container/index'; |
|
||||
import { PageWrapper } from '/@/components/Page'; |
|
||||
|
|
||||
export default defineComponent({ |
|
||||
components: { LazyContainer, TargetContent, PageWrapper }, |
|
||||
}); |
|
||||
</script> |
|
||||
<style lang="less"> |
|
||||
.lazy-base-demo { |
|
||||
&-wrap { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
width: 50%; |
|
||||
height: 2000px; |
|
||||
margin: 20px auto; |
|
||||
background-color: @component-background; |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
&-box { |
|
||||
width: 300px; |
|
||||
height: 300px; |
|
||||
} |
|
||||
|
|
||||
h1 { |
|
||||
height: 1300px; |
|
||||
margin: 20px 0; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.custom-enter { |
|
||||
transform: scale(0.4) translate(100%); |
|
||||
opacity: 0; |
|
||||
} |
|
||||
|
|
||||
.custom-enter-to { |
|
||||
opacity: 1; |
|
||||
} |
|
||||
|
|
||||
.custom-enter-active { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
width: 100%; |
|
||||
transition: all 0.5s; |
|
||||
} |
|
||||
|
|
||||
.custom-leave { |
|
||||
opacity: 1; |
|
||||
} |
|
||||
|
|
||||
.custom-leave-to { |
|
||||
transform: scale(0.4) translate(-100%); |
|
||||
opacity: 0; |
|
||||
} |
|
||||
|
|
||||
.custom-leave-active { |
|
||||
transition: all 0.5s; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,52 +0,0 @@ |
|||||
<template> |
|
||||
<PageWrapper title="懒加载基础示例" content="向下滚动到可见区域才会加载组件"> |
|
||||
<div class="lazy-base-demo-wrap"> |
|
||||
<h1>向下滚动</h1> |
|
||||
|
|
||||
<div class="lazy-base-demo-box"> |
|
||||
<LazyContainer> |
|
||||
<TargetContent /> |
|
||||
<template #skeleton> |
|
||||
<Skeleton :rows="10" /> |
|
||||
</template> |
|
||||
</LazyContainer> |
|
||||
</div> |
|
||||
</div> |
|
||||
</PageWrapper> |
|
||||
</template> |
|
||||
<script lang="ts"> |
|
||||
import { defineComponent } from 'vue'; |
|
||||
import { Skeleton } from 'ant-design-vue'; |
|
||||
import TargetContent from './TargetContent.vue'; |
|
||||
import { LazyContainer } from '/@/components/Container/index'; |
|
||||
import { PageWrapper } from '/@/components/Page'; |
|
||||
|
|
||||
export default defineComponent({ |
|
||||
components: { LazyContainer, PageWrapper, TargetContent, Skeleton }, |
|
||||
}); |
|
||||
</script> |
|
||||
<style lang="less"> |
|
||||
.lazy-base-demo { |
|
||||
&-wrap { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
width: 50%; |
|
||||
height: 2000px; |
|
||||
margin: 20px auto; |
|
||||
background-color: @component-background; |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
&-box { |
|
||||
width: 300px; |
|
||||
height: 300px; |
|
||||
} |
|
||||
|
|
||||
h1 { |
|
||||
height: 1300px; |
|
||||
margin: 20px 0; |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
||||
Loading…
Reference in new issue