mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-03-01 13:36:55 +08:00
refactor(affix): set default listen-to to document. rename offset-* to trigger-*.
This commit is contained in:
parent
d464ec01a5
commit
c5b7645f39
@ -2,10 +2,23 @@
|
||||
|
||||
## Pending
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `n-affix`'s `listen-to` prop is `document` by default (first scrollable parent before).
|
||||
|
||||
### Feats
|
||||
|
||||
- `n-affix`'s `listen-to` prop support `Window | Document | HTMLElement`.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix `n-input-number` not restore valid value after blur.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `n-affix`'s `offset-top` prop is deprecated, please use `trigger-top` instead.
|
||||
- `n-affix`'s `offset-bottom` prop is deprecated, please use `trigger-bottom` instead.
|
||||
|
||||
## 2.10.0
|
||||
|
||||
### Breaking Changes
|
||||
|
@ -2,10 +2,23 @@
|
||||
|
||||
## Pending
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `n-affix` 的 `listen-to` 属性默认为 `document` (曾为首个可滚动的父节点)
|
||||
|
||||
### Feats
|
||||
|
||||
- `n-affix` 的 `listen-to` 属性支持 `Window | Document | HTMLElement`
|
||||
|
||||
### Fixes
|
||||
|
||||
- 修正 `n-input-number` 在 blur 后不会恢复有效的值
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `n-affix`'s `offset-top` prop is deprecated, please use `trigger-top` instead.
|
||||
- `n-affix`'s `offset-bottom` prop is deprecated, please use `trigger-bottom` instead.
|
||||
|
||||
## 2.10.0
|
||||
|
||||
### Breaking Changes
|
||||
|
@ -1,21 +1,29 @@
|
||||
# Basic
|
||||
|
||||
Affix has `offset-top`, `top`, `offset-bottom` and `bottom`. `offset-top` is top affixing trigger point. `top` is the style `top` value after top affixing is trigger. `offset-bottom` and `bottom` work in similar way.
|
||||
Affix has `trigger-top`, `top`, `trigger-bottom` and `bottom`. `trigger-top` is top affixing trigger point. `top` is the style `top` value after top affixing is trigger. `trigger-bottom` and `bottom` work in similar way.
|
||||
|
||||
```html
|
||||
<div class="container">
|
||||
<div class="container" ref="container">
|
||||
<div class="padding"></div>
|
||||
<div class="content">
|
||||
<n-row>
|
||||
<n-col :span="12">
|
||||
<n-affix :top="120" :offset-top="60"
|
||||
><n-tag>Affix Trigger Top 60px</n-tag></n-affix
|
||||
<n-affix
|
||||
:top="120"
|
||||
:trigger-top="60"
|
||||
:listen-to="() => $refs.container"
|
||||
>
|
||||
<n-tag>Affix Trigger Top 60px</n-tag>
|
||||
</n-affix>
|
||||
</n-col>
|
||||
<n-col :span="12">
|
||||
<n-affix :bottom="120" :offset-bottom="60"
|
||||
><n-tag>Affix Trigger Bottom 60px</n-tag></n-affix
|
||||
<n-affix
|
||||
:bottom="120"
|
||||
:trigger-bottom="60"
|
||||
:listen-to="() => $refs.container"
|
||||
>
|
||||
<n-tag>Affix Trigger Bottom 60px</n-tag>
|
||||
</n-affix>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</div>
|
||||
|
@ -13,9 +13,9 @@ position
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| bottom | `number` | `undefined` | The css bottom property after trigger bottom affix. (if not set, use `offset-bottom` prop) |
|
||||
| listen-to | `string \| HTMLElement` | `undefined` | The scrolling element to listen scrolling. If not set it will listen to the nearest scrollable ascendant element. |
|
||||
| offset-bottom | `number` | `undefined` | The distance px to bottom of target to trigger bottom affix. (if not set, use `bottom` prop) |
|
||||
| offset-top | `number` | `undefined` | The distance px to top of target to trigger top affix. (if not set, use `top` prop) |
|
||||
| bottom | `number` | `undefined` | The css bottom property after trigger bottom affix. (if not set, use `trigger-bottom` prop) |
|
||||
| listen-to | `string \| HTMLElement \| Document \| Window` | `document` | The scrolling element to listen scrolling. |
|
||||
| trigger-bottom | `number` | `undefined` | The distance px to bottom of target to trigger bottom affix. (if not set, use `bottom` prop) |
|
||||
| trigger-top | `number` | `undefined` | The distance px to top of target to trigger top affix. (if not set, use `top` prop) |
|
||||
| position | `'fixed' \| 'absolute'` | `'fixed'` | |
|
||||
| top | `number` | `undefined` | The css top property after trigger top affix. (if not set, use `offset-top` prop) |
|
||||
| top | `number` | `undefined` | The css top property after trigger top affix. (if not set, use `trigger-top` prop) |
|
||||
|
@ -1,21 +1,29 @@
|
||||
# Position
|
||||
|
||||
Affix can be `absolute` or `fixed` positioned. You may need some css tricks to make it works as following. By default position is set to `fixed`, because in most cases scrolled element is `#document`.
|
||||
Affix can be `absolute` or `fixed` positioned. You may need some css tricks to make it works as following. By default position is set to `fixed`, because in most cases scrolled element is `document`.
|
||||
|
||||
```html
|
||||
<div class="absolute-anchor-container">
|
||||
<div class="container">
|
||||
<div class="container" ref="container">
|
||||
<div class="padding"></div>
|
||||
<div class="content">
|
||||
<div style="display: inline-block; width: 50%;">
|
||||
<n-affix :offset-top="50" position="absolute"
|
||||
><n-tag>Affix Trigger Top 50px</n-tag></n-affix
|
||||
<n-affix
|
||||
:trigger-top="50"
|
||||
position="absolute"
|
||||
:listen-to="() => $refs.container"
|
||||
>
|
||||
<n-tag>Affix Trigger Top 50px</n-tag>
|
||||
</n-affix>
|
||||
</div>
|
||||
<div style="display: inline-block; width: 50%;">
|
||||
<n-affix :offset-bottom="60" position="absolute"
|
||||
><n-tag>Affix Trigger Bottom 60px</n-tag></n-affix
|
||||
<n-affix
|
||||
:trigger-bottom="60"
|
||||
position="absolute"
|
||||
:listen-to="() => $refs.container"
|
||||
>
|
||||
<n-tag>Affix Trigger Bottom 60px</n-tag>
|
||||
</n-affix>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,23 +1,31 @@
|
||||
# 基础用法
|
||||
|
||||
Affix 有 `offset-top`、`top`、`offset-bottom` 和 `bottom` 属性。`offset-top` 是顶部固定的触发距离,`top` 是在触发顶部固定之后 CSS 的 `top` 值。`offset-bottom` 和 `bottom` 类似。
|
||||
Affix 有 `trigger-top`、`top`、`trigger-bottom` 和 `bottom` 属性。`trigger-top` 是顶部固定的触发距离,`top` 是在触发顶部固定之后 CSS 的 `top` 值。`trigger-bottom` 和 `bottom` 类似。
|
||||
|
||||
```html
|
||||
<div class="container">
|
||||
<div class="container" ref="container">
|
||||
<div class="padding"></div>
|
||||
<div class="content">
|
||||
<n-row>
|
||||
<n-col :span="12">
|
||||
<n-affix :top="120" :offset-top="60"
|
||||
><n-tag>顶部触发距离 60px</n-tag></n-affix
|
||||
<n-grid :cols="2">
|
||||
<n-gi :span="1">
|
||||
<n-affix
|
||||
:top="120"
|
||||
:trigger-top="60"
|
||||
:listen-to="() => $refs.container"
|
||||
>
|
||||
</n-col>
|
||||
<n-col :span="12">
|
||||
<n-affix :bottom="120" :offset-bottom="60"
|
||||
><n-tag>底部触发距离 60px</n-tag></n-affix
|
||||
<n-tag>顶部触发距离 60px</n-tag>
|
||||
</n-affix>
|
||||
</n-gi>
|
||||
<n-gi :span="1">
|
||||
<n-affix
|
||||
:bottom="120"
|
||||
:trigger-bottom="60"
|
||||
:listen-to="() => $refs.container"
|
||||
>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-tag>底部触发距离 60px</n-tag>
|
||||
</n-affix>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
@ -13,9 +13,9 @@ position
|
||||
|
||||
| 名称 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| bottom | `number` | `undefined` | 在触发顶部固定后 Affix 的 CSS bottom 属性(如果没设定,会使用 `offset-bottom` 代替) |
|
||||
| listen-to | `string \| HTMLElement` | `undefined` | 需要监听滚动的元素,如果未设定则会监听最近的可滚动祖先元素 |
|
||||
| offset-bottom | `number` | `undefined` | 触发底部固定时,Affix 和目标元素元素的底部距离(如果没设定,会使用 `bottom` 代替) |
|
||||
| offset-top | `number` | `undefined` | 触发顶部固定时,Affix 和目标元素元素的顶部距离(如果没设定,会使用 `top` 代替) |
|
||||
| bottom | `number` | `undefined` | 在触发顶部固定后 Affix 的 CSS bottom 属性(如果没设定,会使用 `trigger-bottom` 代替) |
|
||||
| listen-to | `string \| HTMLElement \| Document \| Window` | `document` | 需要监听滚动的元素 |
|
||||
| trigger-bottom | `number` | `undefined` | 触发底部固定时,Affix 和目标元素元素的底部距离(如果没设定,会使用 `bottom` 代替) |
|
||||
| trigger-top | `number` | `undefined` | 触发顶部固定时,Affix 和目标元素元素的顶部距离(如果没设定,会使用 `top` 代替) |
|
||||
| position | `'fixed' \| 'absolute'` | `'fixed'` | |
|
||||
| top | `number` | `undefined` | 在触发顶部固定后 Affix 的 CSS top 属性(如果没设定,会使用 `offset-top` 代替) |
|
||||
| top | `number` | `undefined` | 在触发顶部固定后 Affix 的 CSS top 属性(如果没设定,会使用 `trigger-top` 代替) |
|
||||
|
@ -1,21 +1,29 @@
|
||||
# 位置
|
||||
|
||||
Affix 可以 `absolute` 或者 `fixed` 定位。你可能还需要写一些额外的 CSS 才能让达到例子的效果。 默认情况下位置是 `fixed`,因为大多数情况下,滚动的元素是 `#document`。
|
||||
Affix 可以 `absolute` 或者 `fixed` 定位。你可能还需要写一些额外的 CSS 才能让达到例子的效果。 默认情况下位置是 `fixed`,因为大多数情况下,滚动的元素是 `document`。
|
||||
|
||||
```html
|
||||
<div class="absolute-anchor-container">
|
||||
<div class="container">
|
||||
<div class="container" ref="container">
|
||||
<div class="padding"></div>
|
||||
<div class="content">
|
||||
<div style="display: inline-block; width: 50%;">
|
||||
<n-affix :offset-top="50" position="absolute"
|
||||
><n-tag>顶部触发距离 50px</n-tag></n-affix
|
||||
<n-affix
|
||||
:trigger-top="50"
|
||||
position="absolute"
|
||||
:listen-to="() => $refs.container"
|
||||
>
|
||||
<n-tag>顶部触发距离 50px</n-tag>
|
||||
</n-affix>
|
||||
</div>
|
||||
<div style="display: inline-block; width: 50%;">
|
||||
<n-affix :offset-bottom="60" position="absolute"
|
||||
><n-tag>底部触发距离 60px</n-tag></n-affix
|
||||
<n-affix
|
||||
:trigger-bottom="60"
|
||||
position="absolute"
|
||||
:listen-to="() => $refs.container"
|
||||
>
|
||||
<n-tag>底部触发距离 60px</n-tag>
|
||||
</n-affix>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,44 +8,59 @@ import {
|
||||
PropType,
|
||||
h
|
||||
} from 'vue'
|
||||
import { getScrollParent, unwrapElement, beforeNextFrameOnce } from 'seemly'
|
||||
import { unwrapElement, beforeNextFrameOnce } from 'seemly'
|
||||
import { useConfig, useStyle } from '../../_mixins'
|
||||
import { warn, keysOf } from '../../_utils'
|
||||
import type { ExtractPublicPropTypes } from '../../_utils'
|
||||
import { getScrollTop, getRect } from './utils'
|
||||
import type { ScrollTarget } from './utils'
|
||||
import style from './styles/index.cssr'
|
||||
|
||||
export const affixProps = {
|
||||
listenTo: {
|
||||
type: [String, Object] as PropType<
|
||||
string | (() => HTMLElement) | undefined
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
offsetTop: {
|
||||
type: Number,
|
||||
default: undefined
|
||||
},
|
||||
top: {
|
||||
type: Number,
|
||||
default: undefined
|
||||
},
|
||||
offsetBottom: {
|
||||
type: Number,
|
||||
default: undefined
|
||||
},
|
||||
bottom: {
|
||||
type: Number,
|
||||
default: undefined
|
||||
},
|
||||
listenTo: [String, Object, Function] as PropType<
|
||||
string | ScrollTarget | (() => HTMLElement) | undefined
|
||||
>,
|
||||
top: Number,
|
||||
bottom: Number,
|
||||
triggerTop: Number,
|
||||
triggerBottom: Number,
|
||||
position: {
|
||||
type: String,
|
||||
type: String as PropType<'fix' | 'absolute'>,
|
||||
default: 'fix'
|
||||
},
|
||||
// deprecated
|
||||
offsetTop: {
|
||||
type: Number as PropType<number | undefined>,
|
||||
validator: () => {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
'affix',
|
||||
'`offset-top` is deprecated, please use `trigger-top` instead.'
|
||||
)
|
||||
}
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
offsetBottom: {
|
||||
type: Number as PropType<number | undefined>,
|
||||
validator: () => {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
'affix',
|
||||
'`offset-bottom` is deprecated, please use `trigger-bottom` instead.'
|
||||
)
|
||||
}
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
target: {
|
||||
type: (Function as unknown) as PropType<(() => HTMLElement) | undefined>,
|
||||
validator: () => {
|
||||
warn('affix', '`target` is deprecated, please use `listen-to` instead.')
|
||||
if (__DEV__) {
|
||||
warn('affix', '`target` is deprecated, please use `listen-to` instead.')
|
||||
}
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
@ -62,7 +77,7 @@ export default defineComponent({
|
||||
setup (props) {
|
||||
const { mergedClsPrefixRef } = useConfig(props)
|
||||
useStyle('Affix', style, mergedClsPrefixRef)
|
||||
const scrollElementRef = ref<HTMLElement | null>(null)
|
||||
let scrollTarget: ScrollTarget | null = null
|
||||
const stickToTopRef = ref(false)
|
||||
const stickToBottomRef = ref(false)
|
||||
const bottomAffixedTriggerScrollTopRef = ref<number | null>(null)
|
||||
@ -71,69 +86,55 @@ export default defineComponent({
|
||||
return stickToBottomRef.value || stickToTopRef.value
|
||||
})
|
||||
const mergedOffsetTopRef = computed(() => {
|
||||
const { offsetTop, top } = props
|
||||
return offsetTop === undefined ? top : offsetTop
|
||||
return props.triggerTop ?? props.offsetTop ?? props.top
|
||||
})
|
||||
const mergedTopRef = computed(() => {
|
||||
const { offsetTop, top } = props
|
||||
return top === undefined ? offsetTop : top
|
||||
return props.top ?? props.triggerTop ?? props.offsetTop
|
||||
})
|
||||
const mergedBottomRef = computed(() => {
|
||||
const { offsetBottom, bottom } = props
|
||||
return bottom === undefined ? offsetBottom : bottom
|
||||
return props.bottom ?? props.triggerBottom ?? props.offsetBottom
|
||||
})
|
||||
const mergedOffsetBottomRef = computed(() => {
|
||||
const { offsetBottom, bottom } = props
|
||||
return offsetBottom === undefined ? bottom : offsetBottom
|
||||
return props.triggerBottom ?? props.offsetBottom ?? props.bottom
|
||||
})
|
||||
const selfRef = ref<Element | null>(null)
|
||||
const init = (): void => {
|
||||
const { target: getScrollTarget, listenTo } = props
|
||||
let scrollElement
|
||||
if (getScrollTarget) {
|
||||
// deprecated
|
||||
scrollElement = getScrollTarget()
|
||||
scrollTarget = getScrollTarget()
|
||||
} else if (listenTo) {
|
||||
scrollElement = unwrapElement(listenTo)
|
||||
scrollTarget = unwrapElement(listenTo)
|
||||
} else {
|
||||
scrollElement = getScrollParent(selfRef.value)
|
||||
scrollTarget = document
|
||||
}
|
||||
if (scrollElement) {
|
||||
scrollElementRef.value = scrollElement
|
||||
if (scrollTarget) {
|
||||
scrollTarget.addEventListener('scroll', handleScroll)
|
||||
handleScroll()
|
||||
} else if (__DEV__) {
|
||||
warn('affix', 'Target to be listened to is not valid.')
|
||||
}
|
||||
if (scrollElement) {
|
||||
scrollElement.addEventListener('scroll', handleScroll)
|
||||
handleScroll()
|
||||
}
|
||||
}
|
||||
function handleScroll (): void {
|
||||
beforeNextFrameOnce(_handleScroll)
|
||||
}
|
||||
|
||||
function _handleScroll (): void {
|
||||
const { value: containerEl } = scrollElementRef
|
||||
const { value: selfEl } = selfRef
|
||||
if (!containerEl || !selfEl) return
|
||||
if (!scrollTarget || !selfEl) return
|
||||
const scrollTop = getScrollTop(scrollTarget)
|
||||
if (affixedRef.value) {
|
||||
if (
|
||||
containerEl.scrollTop <
|
||||
(topAffixedTriggerScrollTopRef.value as number)
|
||||
) {
|
||||
if (scrollTop < (topAffixedTriggerScrollTopRef.value as number)) {
|
||||
stickToTopRef.value = false
|
||||
topAffixedTriggerScrollTopRef.value = null
|
||||
}
|
||||
if (
|
||||
containerEl.scrollTop >
|
||||
(bottomAffixedTriggerScrollTopRef.value as number)
|
||||
) {
|
||||
if (scrollTop > (bottomAffixedTriggerScrollTopRef.value as number)) {
|
||||
stickToBottomRef.value = false
|
||||
bottomAffixedTriggerScrollTopRef.value = null
|
||||
}
|
||||
return
|
||||
}
|
||||
const containerRect = containerEl.getBoundingClientRect()
|
||||
const containerRect = getRect(scrollTarget)
|
||||
const affixRect = selfEl.getBoundingClientRect()
|
||||
const pxToTop = affixRect.top - containerRect.top
|
||||
const pxToBottom = containerRect.bottom - affixRect.bottom
|
||||
@ -142,7 +143,7 @@ export default defineComponent({
|
||||
if (mergedOffsetTop !== undefined && pxToTop <= mergedOffsetTop) {
|
||||
stickToTopRef.value = true
|
||||
topAffixedTriggerScrollTopRef.value =
|
||||
containerEl.scrollTop - (mergedOffsetTop - pxToTop)
|
||||
scrollTop - (mergedOffsetTop - pxToTop)
|
||||
} else {
|
||||
stickToTopRef.value = false
|
||||
topAffixedTriggerScrollTopRef.value = null
|
||||
@ -153,7 +154,7 @@ export default defineComponent({
|
||||
) {
|
||||
stickToBottomRef.value = true
|
||||
bottomAffixedTriggerScrollTopRef.value =
|
||||
containerEl.scrollTop + mergedOffsetBottom - pxToBottom
|
||||
scrollTop + mergedOffsetBottom - pxToBottom
|
||||
} else {
|
||||
stickToBottomRef.value = false
|
||||
bottomAffixedTriggerScrollTopRef.value = null
|
||||
@ -163,10 +164,8 @@ export default defineComponent({
|
||||
init()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
const scrollElement = scrollElementRef.value
|
||||
if (scrollElement) {
|
||||
scrollElement.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
if (!scrollTarget) return
|
||||
scrollTarget.removeEventListener('scroll', handleScroll)
|
||||
})
|
||||
return {
|
||||
selfRef,
|
||||
@ -174,12 +173,17 @@ export default defineComponent({
|
||||
mergedClsPrefix: mergedClsPrefixRef,
|
||||
mergedstyle: computed<CSSProperties>(() => {
|
||||
const style: CSSProperties = {}
|
||||
if (stickToTopRef.value && mergedOffsetTopRef.value !== undefined) {
|
||||
if (
|
||||
stickToTopRef.value &&
|
||||
mergedOffsetTopRef.value !== undefined &&
|
||||
mergedTopRef.value !== undefined
|
||||
) {
|
||||
style.top = `${mergedTopRef.value}px`
|
||||
}
|
||||
if (
|
||||
stickToBottomRef.value &&
|
||||
mergedOffsetBottomRef.value !== undefined
|
||||
mergedOffsetBottomRef.value !== undefined &&
|
||||
mergedBottomRef.value !== undefined
|
||||
) {
|
||||
style.bottom = `${mergedBottomRef.value}px`
|
||||
}
|
||||
|
11
src/affix/src/utils.ts
Normal file
11
src/affix/src/utils.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export type ScrollTarget = Window | Document | HTMLElement
|
||||
|
||||
export function getScrollTop (target: ScrollTarget): number {
|
||||
return target instanceof HTMLElement ? target.scrollTop : window.scrollY
|
||||
}
|
||||
|
||||
export function getRect (target: ScrollTarget): { top: number, bottom: number } {
|
||||
return target instanceof HTMLElement
|
||||
? target.getBoundingClientRect()
|
||||
: { top: 0, bottom: window.innerHeight }
|
||||
}
|
Loading…
Reference in New Issue
Block a user