-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #64 from duenyang/feature/loading
feat(loading): add loading adn portal 🎉
- Loading branch information
Showing
22 changed files
with
963 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,146 @@ | ||
import raf from 'raf'; | ||
|
||
import { AttachNode, AttachNodeReturnValue } from '../common'; | ||
import { easeInOutCubic, EasingFunction } from './easing'; | ||
// 用于判断是否可使用 dom | ||
export const canUseDocument = !!(typeof window !== 'undefined' && window.document && window.document.createElement); | ||
|
||
// 获取 css vars | ||
export const getCssVarsValue = (name: string, element?: HTMLElement) => { | ||
if (!canUseDocument) return; | ||
|
||
const el = element || document.documentElement; | ||
return getComputedStyle(el).getPropertyValue(name); | ||
}; | ||
|
||
function isWindow(obj: any) { | ||
return obj && obj === obj.window; | ||
} | ||
|
||
type ScrollTarget = HTMLElement | Window | Document; | ||
|
||
export function getScroll(target: ScrollTarget, isLeft?: boolean): number { | ||
// node环境或者target为空 | ||
if (typeof window === 'undefined' || !target) { | ||
return 0; | ||
} | ||
const method = isLeft ? 'scrollLeft' : 'scrollTop'; | ||
let result = 0; | ||
if (isWindow(target)) { | ||
result = (target as Window)[isLeft ? 'pageXOffset' : 'pageYOffset']; | ||
} else if (target instanceof Document) { | ||
result = target.documentElement[method]; | ||
} else if (target) { | ||
result = (target as HTMLElement)[method]; | ||
} | ||
return result; | ||
} | ||
|
||
interface ScrollTopOptions { | ||
container?: ScrollTarget; | ||
duration?: number; | ||
easing?: EasingFunction; | ||
} | ||
|
||
export const scrollTo = (target: number, opt: ScrollTopOptions) => { | ||
const { container = window, duration = 450, easing = easeInOutCubic } = opt; | ||
const scrollTop = getScroll(container); | ||
const startTime = Date.now(); | ||
return new Promise((res) => { | ||
const fnc = () => { | ||
const timestamp = Date.now(); | ||
const time = timestamp - startTime; | ||
const nextScrollTop = easing(Math.min(time, duration), scrollTop, target, duration); | ||
if (isWindow(container)) { | ||
(container as Window).scrollTo(window.pageXOffset, nextScrollTop); | ||
} else if (container instanceof HTMLDocument || container.constructor.name === 'HTMLDocument') { | ||
(container as HTMLDocument).documentElement.scrollTop = nextScrollTop; | ||
} else { | ||
(container as HTMLElement).scrollTop = nextScrollTop; | ||
} | ||
if (time < duration) { | ||
raf(fnc); | ||
} else { | ||
// 由于上面步骤设置了 scrollTop, 滚动事件可能未触发完毕 | ||
// 此时应该在下一帧再执行 res | ||
raf(res); | ||
} | ||
}; | ||
raf(fnc); | ||
}); | ||
}; | ||
|
||
export function getAttach(attach: AttachNode, triggerNode?: HTMLElement): AttachNodeReturnValue { | ||
if (!canUseDocument) return null; | ||
|
||
let el: AttachNodeReturnValue; | ||
if (typeof attach === 'string') { | ||
el = document.querySelector(attach); | ||
} | ||
if (typeof attach === 'function') { | ||
el = attach(triggerNode); | ||
} | ||
if (typeof attach === 'object') { | ||
if ((attach as any) instanceof window.HTMLElement) { | ||
el = attach; | ||
} | ||
} | ||
|
||
// fix el in iframe | ||
if (el && el.nodeType === 1) return el; | ||
|
||
return document.body; | ||
} | ||
|
||
export const addClass = function (el: Element, cls: string) { | ||
if (!el) return; | ||
let curClass = el.className; | ||
const classes = (cls || '').split(' '); | ||
|
||
for (let i = 0, j = classes.length; i < j; i++) { | ||
const clsName = classes[i]; | ||
if (!clsName) continue; | ||
|
||
if (el.classList) { | ||
el.classList.add(clsName); | ||
} else if (!hasClass(el, clsName)) { | ||
curClass += ` ${clsName}`; | ||
} | ||
} | ||
if (!el.classList) { | ||
// eslint-disable-next-line | ||
el.className = curClass; | ||
} | ||
}; | ||
|
||
const trim = (str: string): string => (str || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); | ||
|
||
export const removeClass = function (el: Element, cls: string) { | ||
if (!el || !cls) return; | ||
const classes = cls.split(' '); | ||
let curClass = ` ${el.className} `; | ||
|
||
for (let i = 0, j = classes.length; i < j; i++) { | ||
const clsName = classes[i]; | ||
if (!clsName) continue; | ||
|
||
if (el.classList) { | ||
el.classList.remove(clsName); | ||
} else if (hasClass(el, clsName)) { | ||
curClass = curClass.replace(` ${clsName} `, ' '); | ||
} | ||
} | ||
if (!el.classList) { | ||
// eslint-disable-next-line | ||
el.className = trim(curClass); | ||
} | ||
}; | ||
|
||
export function hasClass(el: Element, cls: string) { | ||
if (!el || !cls) return false; | ||
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.'); | ||
if (el.classList) { | ||
return el.classList.contains(cls); | ||
} | ||
return ` ${el.className} `.indexOf(` ${cls} `) > -1; | ||
} |
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,42 @@ | ||
/** | ||
* @file | ||
* 缓动函数 | ||
* 参考自: https://github.com/bameyrick/js-easing-functions/blob/master/src/index.ts | ||
*/ | ||
|
||
export interface EasingFunction { | ||
(current: number, start: number, end: number, duration: number): number; | ||
} | ||
|
||
/** | ||
* @export | ||
* @param {number} current 当前时间 | ||
* @param {number} start 开始值 | ||
* @param {number} end 结束值 | ||
* @param {number} duration 持续时间 | ||
* @returns | ||
*/ | ||
export const linear: EasingFunction = function (current, start, end, duration) { | ||
const change = end - start; | ||
const offset = (change * current) / duration; | ||
return offset + start; | ||
}; | ||
|
||
/** | ||
* @export | ||
* @param {number} current 当前时间 | ||
* @param {number} start 开始值 | ||
* @param {number} end 结束值 | ||
* @param {number} duration 持续时间 | ||
* @returns | ||
*/ | ||
export const easeInOutCubic: EasingFunction = function (current, start, end, duration) { | ||
const change = (end - start) / 2; | ||
let time = current / (duration / 2); | ||
if (time < 1) { | ||
return change * time * time * time + start; | ||
} | ||
time -= 2; | ||
// eslint-disable-next-line no-return-assign | ||
return change * (time * time * time + 2) + start; | ||
}; |
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,5 @@ | ||
import _Portal from './portal'; | ||
|
||
export type { PortalProps } from './portal'; | ||
|
||
export const Partal = _Portal; |
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,70 @@ | ||
import { Component, render, tag } from 'omi'; | ||
|
||
import { getClassPrefix } from '../_util/classname'; | ||
import { canUseDocument } from '../_util/dom'; | ||
import { AttachNode, AttachNodeReturnValue, TNode } from '../common'; | ||
|
||
export interface PortalProps { | ||
/** | ||
* 指定挂载的 HTML 节点, false 为挂载在 body | ||
*/ | ||
attach?: AttachNode; | ||
/** | ||
* 触发元素 | ||
*/ | ||
triggerNode?: HTMLElement; | ||
children: TNode; | ||
} | ||
|
||
export function getAttach(attach: PortalProps['attach'], triggerNode?: HTMLElement): AttachNodeReturnValue { | ||
if (!canUseDocument) return null; | ||
|
||
let el: AttachNodeReturnValue; | ||
if (typeof attach === 'string') { | ||
el = document.querySelector(attach); | ||
} | ||
if (typeof attach === 'function') { | ||
el = attach(triggerNode); | ||
} | ||
if (typeof attach === 'object' && (attach as any) instanceof window.HTMLElement) { | ||
el = attach; | ||
} | ||
|
||
// fix el in iframe | ||
if (el && el.nodeType === 1) return el; | ||
|
||
return document.body; | ||
} | ||
|
||
@tag('t-portal') | ||
export default class Portal extends Component<PortalProps> { | ||
static css = []; | ||
|
||
static defaultProps = { | ||
attach: false, | ||
}; | ||
|
||
parentElement: HTMLElement | null = null; | ||
|
||
container: HTMLElement = null; | ||
|
||
classPrefix = getClassPrefix(); | ||
|
||
getContainer() { | ||
if (!canUseDocument) return null; | ||
const el = document.createElement('div'); | ||
el.className = `${this.classPrefix}-portal-wrapper`; | ||
return el; | ||
} | ||
|
||
install(): void { | ||
this.container = this.getContainer(); | ||
this.parentElement = getAttach(this.props.attach, this.props.triggerNode) as HTMLElement; | ||
this.parentElement?.appendChild?.(this.container); | ||
} | ||
|
||
render() { | ||
const { children } = this.props; | ||
return render(children, this.container); | ||
} | ||
} |
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,89 @@ | ||
--- | ||
title: Loading 加载中 | ||
description: 在网络较慢或数据较多时,表示数据正在加载的状态。 | ||
isComponent: true | ||
usage: { title: '', description: '' } | ||
spline: message | ||
--- | ||
|
||
### 图标加载 | ||
|
||
加载过程中只有图标显示。适用于打开页面或操作完成后模块内等待刷新的加载场景。 | ||
|
||
{{ base }} | ||
|
||
### 文字加载 | ||
|
||
加载过程中有文字显示。适用于打开页面或操作完成后模块内等待刷新的加载场景。 | ||
|
||
{{ text }} | ||
|
||
### 文字和图标共同显示加载 | ||
|
||
加载过程中有文字和图标共同显示。适用于打开页面或操作完成后页面内等待刷新的加载场景。 | ||
|
||
{{ icon-text }} | ||
|
||
### 不同尺寸的加载 | ||
小尺寸适用于组件内加载场景,中尺寸适用于容器如卡片、表格等区域的加载场景,大尺寸适用于页面全屏加载场景。 | ||
|
||
{{ size }} | ||
|
||
### 有包裹的加载 | ||
Loading 组件可以作为容器包裹需要显示加载状态的内容。 | ||
|
||
{{ wrap }} | ||
|
||
### 有延时的加载 | ||
设置最短延迟响应时间,低于响应时间的操作不显示加载状态。 | ||
|
||
{{ delay }} | ||
|
||
### 全屏加载 | ||
全屏展示加载状态,阻止用户操作。 | ||
|
||
{{ fullscreen }} | ||
|
||
### 挂载到指定元素 | ||
|
||
可通过 `attach` 挂载到指定元素。 | ||
|
||
注:被挂载元素(loading的父元素)需设置:`position: relative;` | ||
|
||
{{ attach }} | ||
|
||
### 函数方式调用 | ||
|
||
{{ service }} | ||
|
||
|
||
## API | ||
### Loading Props | ||
|
||
名称 | 类型 | 默认值 | 说明 | 必传 | ||
-- | -- | -- | -- | -- | ||
className | String | - | 类名 | N | ||
style | Object | - | 样式,TS 类型:`Styles` | N | ||
attach | String / Function | '' | 挂载元素,默认挂载到组件本身所在的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`AttachNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N | ||
children | TNode | - | 子元素,同 content。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N | ||
content | TNode | - | 子元素。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N | ||
delay | Number | 0 | 延迟显示加载效果的时间,用于防止请求速度过快引起的加载闪烁,单位:毫秒 | N | ||
fullscreen | Boolean | false | 是否显示为全屏加载 | N | ||
indicator | TNode | true | 加载指示符,值为 true 显示默认指示符,值为 false 则不显示,也可以自定义指示符。TS 类型:`boolean \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N | ||
inheritColor | Boolean | false | 是否继承父元素颜色 | N | ||
loading | Boolean | true | 是否处于加载状态 | N | ||
preventScrollThrough | Boolean | true | 防止滚动穿透,全屏加载模式有效 | N | ||
showOverlay | Boolean | true | 是否需要遮罩层,遮罩层对包裹元素才有效 | N | ||
size | String | medium | 尺寸,示例:small/medium/large/12px/56px/0.3em | N | ||
text | TNode | - | 加载提示文案。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N | ||
zIndex | Number | - | 消息通知层级,样式默认为 3500 | N | ||
|
||
### loading 或 LoadingPlugin | ||
|
||
这是一个插件函数,参数形式为顺序参数(形如:(a, b, c)),而非对象参数(形如:`({ a, b, c })`)。顺序参数如下, | ||
|
||
参数名称 | 参数类型 | 参数默认值 | 参数说明 | ||
-- | -- | -- | -- | ||
options | Function | - | 必需。TS 类型:`boolean \| TdLoadingProps` | ||
|
||
插件返回值:`LoadingInstance【interface LoadingInstance { hide: () => void }】` |
Oops, something went wrong.