-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(component): add affix component (#152)
* feat(component): affix component * chore: add example for affix component * docs: add affix component doc
- Loading branch information
1 parent
c209831
commit 33f0e5c
Showing
15 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
--- | ||
title: Affix | ||
lang: en-US | ||
--- | ||
|
||
# Affix <new-badge/> | ||
|
||
Fix the element to a specific visible area. | ||
|
||
## Basic usage | ||
|
||
Affix is fixed at the top of the page by default. | ||
|
||
You can set `offset` attribute to change the offset top,the default value is 0. | ||
|
||
<demo src="../example/affix/basic.vue"></demo> | ||
|
||
## Target Container | ||
|
||
You can set `target` attribute to keep the affix in the container at all times. It will be hidden if out of range. | ||
|
||
Please notice that the container avoid having scrollbar. | ||
|
||
<demo src="../example/affix/target.vue"></demo> | ||
|
||
## Fixed Position | ||
|
||
The affix component provides two fixed positions: `top` and `bottom`. | ||
|
||
You can set `position` attribute to change the fixed position, the default value is `top`. | ||
|
||
<demo src="../example/affix/fixed.vue"></demo> | ||
|
||
## Affix Props | ||
| Name | Type | Default | Description | | ||
| --- | --- | --- | --- | | ||
| offset | `number` | `0` | Offset distance. | ||
| position | `'top' \| 'bottom'` | `'top'` | Position of affix. | ||
| target | `string` | `''` | Target container. (CSS selector) | | ||
| z-index | `number` | `100` | `z-index` of affix | | ||
|
||
|
||
## Affix Methods | ||
| Name | Parameters | Description | | ||
| --- | --- | --- | | ||
| change | `(fixed: boolean) => void` | Triggers when fixed state changed. | | ||
| scroll | `(value: { scrollTop: number, fixed: boolean }) => void` | Triggers when scrolling. | | ||
|
||
|
||
## Affix Slots | ||
| Name | Parameters | Description | | ||
| --- | --- | --- | | ||
| default | `()` | Customize default content. | | ||
|
||
## Affix Exposes | ||
| Name | Parameters | Description | | ||
| --- | --- | --- | | ||
| update | `() => void` | Update affix state manually. | | ||
| updateRoot | `() => void` | Update rootRect info. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<template> | ||
<div fscw gap-2> | ||
<o-affix> | ||
<o-button shadow dashed type="primary"> | ||
Offset top 0px | ||
</o-button> | ||
</o-affix> | ||
<o-affix :offset="120"> | ||
<o-button shadow dashed type="secondary"> | ||
Offset top 120px | ||
</o-button> | ||
</o-affix> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<template> | ||
<div fscw gap-2> | ||
<o-affix position="bottom" :offset="20"> | ||
<o-button shadow dashed type="success"> | ||
Offset bottom 20px | ||
</o-button> | ||
</o-affix> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<template> | ||
<div class="affix-container text-center w-full h-400px rounded-4px bg-blue-300"> | ||
<o-affix target=".affix-container" :offset="80"> | ||
<o-button shadow dashed type="primary"> | ||
Target container | ||
</o-button> | ||
</o-affix> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<script setup lang='ts'> | ||
</script> | ||
|
||
<template> | ||
<OCard title="Affix"> | ||
<div space-y-2> | ||
<div fsc gap-2> | ||
<o-affix> | ||
<o-button shadow dashed type="primary"> | ||
Offset top 0px | ||
</o-button> | ||
</o-affix> | ||
<o-affix :offset="40"> | ||
<o-button shadow dashed type="secondary"> | ||
Offset top 40px | ||
</o-button> | ||
</o-affix> | ||
</div> | ||
<div fsc gap-2> | ||
<div class="affix-container text-center w-full h-400px rounded-4px bg-blue-300"> | ||
<o-affix target=".affix-container" :offset="80"> | ||
<o-button shadow dashed type="primary"> | ||
Target container | ||
</o-button> | ||
</o-affix> | ||
</div> | ||
</div> | ||
<div fsc gap-2> | ||
<o-affix position="bottom" :offset="20"> | ||
<o-button shadow dashed type="success"> | ||
Offset bottom 20px | ||
</o-button> | ||
</o-affix> | ||
</div> | ||
</div> | ||
</OCard> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
<TheBadge /> | ||
<TheTag /> --> | ||
<TheMessage /> | ||
<TheAffix /> | ||
<TheEmpty /> | ||
<TheLink /> | ||
<TheRadio /> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { withInstall } from '@onu-ui/utils' | ||
import Affix from './src/affix.vue' | ||
|
||
export const OAffix = withInstall(Affix) | ||
export default OAffix | ||
|
||
export * from './src/affix' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import type { ExtractPropTypes } from 'vue' | ||
import { isBoolean, isNumber } from './../../../utils/shared/is' | ||
|
||
export const affixProps = { | ||
zIndex: { | ||
type: Number, | ||
default: 100, | ||
}, | ||
target: { | ||
type: String, | ||
default: '', | ||
}, | ||
offset: { | ||
type: Number, | ||
default: 0, | ||
}, | ||
position: { | ||
type: String, | ||
values: ['top', 'bottom'], | ||
default: 'top', | ||
}, | ||
} as const | ||
|
||
export type OAffixProps = ExtractPropTypes<typeof affixProps> | ||
|
||
export const affixEmits = { | ||
scroll: ({ scrollTop, fixed }: { scrollTop: number; fixed: boolean }) => isNumber(scrollTop) && isBoolean(fixed), | ||
change: (fixed: boolean) => isBoolean(fixed), | ||
} | ||
export type OAffixEmits = typeof affixEmits |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
<script setup lang="ts" name="OAffix"> | ||
import { getScrollContainer } from '@onu-ui/utils' | ||
import type { CSSProperties } from 'vue' | ||
import { affixEmits, affixProps } from './affix' | ||
const props = defineProps(affixProps) | ||
const emit = defineEmits(affixEmits) | ||
const target = shallowRef<HTMLElement>() | ||
const root = shallowRef<HTMLDivElement>() | ||
const scrollContainer = shallowRef<HTMLElement | Window>() | ||
const { height: windowHeight } = useWindowSize() | ||
const { | ||
height: rootHeight, | ||
width: rootWidth, | ||
top: rootTop, | ||
bottom: rootBottom, | ||
update: updateRoot, | ||
} = useElementBounding(root, { windowScroll: false }) | ||
const targetRect = useElementBounding(target) | ||
const fixed = ref(false) | ||
const scrollTop = ref(0) | ||
const transform = ref(0) | ||
const rootStyle = computed<CSSProperties>(() => { | ||
return { | ||
height: fixed.value ? `${rootHeight.value}px` : '', | ||
width: fixed.value ? `${rootWidth.value}px` : '', | ||
} | ||
}) | ||
const affixStyle = computed<CSSProperties>(() => { | ||
if (!fixed.value) | ||
return {} | ||
const offset = props.offset ? `${props.offset}px` : 0 | ||
return { | ||
height: `${rootHeight.value}px`, | ||
width: `${rootWidth.value}px`, | ||
top: props.position === 'top' ? offset : '', | ||
bottom: props.position === 'bottom' ? offset : '', | ||
transform: transform.value ? `translateY(${transform.value}px)` : '', | ||
zIndex: props.zIndex, | ||
} | ||
}) | ||
const update = () => { | ||
if (!scrollContainer.value) | ||
return | ||
const isTop = props.position === 'top' | ||
const isTarget = props.target | ||
const isFixed | ||
= (isTop && isTarget) | ||
? (props.offset > rootTop.value && targetRect.bottom.value > 0) | ||
: isTop | ||
? props.offset > rootTop.value | ||
: isTarget | ||
? (windowHeight.value - props.offset < rootBottom.value && windowHeight.value > targetRect.top.value) | ||
: windowHeight.value - props.offset < rootBottom.value | ||
const difference | ||
= (isTop && isTarget) | ||
? targetRect.bottom.value - props.offset - rootHeight.value | ||
: (!isTop && isTarget) | ||
? windowHeight.value - targetRect.top.value - props.offset - rootHeight.value | ||
: 0 | ||
scrollTop.value = scrollContainer.value instanceof Window ? document.documentElement.scrollTop : (scrollContainer.value.scrollTop || 0) | ||
transform.value = difference < 0 ? (isTop ? difference : -difference) : 0 | ||
fixed.value = isFixed | ||
} | ||
const handleScroll = () => { | ||
updateRoot() | ||
emit('scroll', { | ||
scrollTop: scrollTop.value, | ||
fixed: fixed.value, | ||
}) | ||
} | ||
useEventListener(scrollContainer, 'scroll', handleScroll) | ||
watchEffect(update) | ||
watch(fixed, val => emit('change', val)) | ||
onMounted(() => { | ||
const targetElement = props.target | ||
? document.querySelector<HTMLElement>(props.target) | ||
: document.documentElement | ||
if (!targetElement) | ||
throw new Error(`[OAffix] target is not existed: ${props.target}`) | ||
target.value = targetElement | ||
scrollContainer.value = getScrollContainer(root.value!, true) | ||
updateRoot() | ||
}) | ||
defineExpose({ | ||
/** @description update affix status */ | ||
update, | ||
/** @description update rootRect info */ | ||
updateRoot, | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div | ||
ref="root" :style="rootStyle" | ||
> | ||
<div | ||
:class="[ | ||
fixed && `fixed`, | ||
]" | ||
:style="affixStyle" | ||
> | ||
<slot /> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { isClient } from '@vueuse/core' | ||
import { getStyle } from './style' | ||
|
||
export const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => { | ||
if (!isClient) | ||
return false | ||
|
||
const key = ( | ||
{ | ||
undefined: 'overflow', | ||
true: 'overflow-y', | ||
false: 'overflow-x', | ||
} as const | ||
)[String(isVertical)]! | ||
const overflow = getStyle(el, key) | ||
return ['scroll', 'auto', 'overlay'].some(s => overflow.includes(s)) | ||
} | ||
|
||
export const getScrollContainer = ( | ||
el: HTMLElement, | ||
isVertical?: boolean, | ||
): Window | HTMLElement | undefined => { | ||
if (!isClient) | ||
return | ||
|
||
let parent: HTMLElement = el | ||
while (parent) { | ||
if ([window, document, document.documentElement].includes(parent)) | ||
return window | ||
|
||
if (isScroll(parent, isVertical)) | ||
return parent | ||
|
||
parent = parent.parentNode as HTMLElement | ||
} | ||
|
||
return parent | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { isClient } from '@vueuse/core' | ||
import type { CSSProperties } from 'vue' | ||
import { camelize } from 'vue' | ||
|
||
export const getStyle = ( | ||
element: HTMLElement, | ||
styleName: keyof CSSProperties, | ||
): string => { | ||
if (!isClient || !element || !styleName) | ||
return '' | ||
|
||
let key = camelize(styleName) | ||
if (key === 'float') | ||
key = 'cssFloat' | ||
try { | ||
const style = (element.style as any)[key] | ||
if (style) | ||
return style | ||
const computed: any = document.defaultView?.getComputedStyle(element, '') | ||
return computed ? computed[key] : '' | ||
} | ||
catch { | ||
return (element.style as any)[key] | ||
} | ||
} |