Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default {
navigationBarTitleText: 'BackTop',
navigationBarTitleText: 'BackTop'
}
3 changes: 2 additions & 1 deletion packages/nutui-taro-demo-rn/src/nav/pages/backtop/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export default <>button</>;
import Demo from '@/packages/backtop/demo.taro';
export default Demo;
2 changes: 1 addition & 1 deletion src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@
"enName": "nav",
"packages": [
{
"version": "2.0.0",
"version": "3.0.0",
"name": "BackTop",
"type": "component",
"cName": "返回顶部",
Expand Down
19 changes: 13 additions & 6 deletions src/packages/backtop/backtop.harmony.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@
display: none;
position: fixed;
}
.nut-backtop.show {
.nut-backtop-rn {
position: absolute;
}
.nut-backtop-show {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
color: #1A1A1A;
color: #1a1a1a;
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 50%;
border-radius: 21px;
z-index: 100;
}
.nut-backtop-show:active {
background: #f5f6fa;
}
.nut-backtop.show:active {
background: #F7F8FC;
.nut-backtop-show-active {
background: #f5f6fa;
}
.nut-backtop-main {
transition: all 0.2s ease-in-out;
color: #1A1A1A;
color: #1a1a1a;
}
12 changes: 9 additions & 3 deletions src/packages/backtop/backtop.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
display: none;
position: fixed;

&.show {
&-rn {
position: absolute;
}

&-show {
width: 40px;
height: 40px;
display: flex;
Expand All @@ -11,9 +15,11 @@
background: $color-background-overlay;
color: $color-title;
border: 1px solid $backtop-border-color;
border-radius: 50%;
border-radius: 21px;
z-index: 100;

&:active {
&:active,
&-active {
background: $color-background;
}
}
Expand Down
102 changes: 83 additions & 19 deletions src/packages/backtop/backtop.taro.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import React, { FunctionComponent, useState } from 'react'
import React, {
FunctionComponent,
useCallback,
useState,
useMemo,
useEffect,
useRef,
} from 'react'
import type { MouseEvent } from 'react'
import { usePageScroll, pageScrollTo } from '@tarojs/taro'
import {
usePageScroll,
pageScrollTo,
PageScrollObject,
getSystemInfo,
} from '@tarojs/taro'
import { View, ITouchEvent } from '@tarojs/components'
import { Top } from '@nutui/icons-react-taro'
import classNames from 'classnames'
import { BasicComponent, ComponentDefaults } from '@/utils/typings'
import { useRtl } from '@/packages/configprovider/index.taro'
import { harmonyAndRn, rn } from '@/utils/platform-taro'
import pxTransform from '@/utils/px-transform'

export interface BackTopProps extends BasicComponent {
threshold: number
zIndex: number
duration: number
/**
* 容器滚动时的回调参数,主要用于 rn、鸿蒙端
*/
scrollRes?: PageScrollObject
onClick?: (event: React.MouseEvent<Element, MouseEvent> | ITouchEvent) => void
}

Expand All @@ -21,59 +39,105 @@ const defaultProps = {
duration: 1000,
} as BackTopProps

const isNative = harmonyAndRn()

export const BackTop: FunctionComponent<
Partial<BackTopProps> & Omit<React.HTMLAttributes<HTMLDivElement>, 'onClick'>
> = (props) => {
const rtl = useRtl()
const { children, threshold, zIndex, className, duration, style, onClick } = {
const {
children,
threshold,
zIndex,
className,
duration,
style,
scrollRes,
onClick,
} = {
...defaultProps,
...props,
}

const classPrefix = 'nut-backtop'

const [backTop, SetBackTop] = useState(false)
const [isTouchStart, setTouchStart] = useState(false)
const systemInfo = useRef<getSystemInfo.Result>({})

useEffect(() => {
getSystemInfo().then((res) => {
systemInfo.current = res
})
}, [])

const handleActiveStart = useCallback(() => {
isNative && setTouchStart(true)
}, [])

const handleActiveEnd = useCallback(() => {
isNative && setTouchStart(false)
}, [])

const onScroll = useCallback(
(res: PageScrollObject) => {
const { scrollTop } = res
scrollTop >= threshold ? SetBackTop(true) : SetBackTop(false)
},
[threshold]
)

// 监听用户滑动页面事件
usePageScroll((res) => {
const { scrollTop } = res
scrollTop >= threshold ? SetBackTop(true) : SetBackTop(false)
})
usePageScroll(onScroll)

useEffect(() => {
if (!scrollRes) return
onScroll(scrollRes)
}, [onScroll, scrollRes])

// 返回顶部点击事件
const goTop = (e: React.MouseEvent<Element, MouseEvent> | ITouchEvent) => {
onClick && onClick(e)
pageScrollTo({
scrollTop: 0,
duration: duration > 0 ? duration : 0,
})
}
const goTop = useCallback(
(e: React.MouseEvent<Element, MouseEvent> | ITouchEvent) => {
onClick?.(e)
pageScrollTo({
scrollTop: 0,
duration: duration > 0 ? duration : 0,
})
},
[duration, onClick]
)

const styles =
Object.keys(style || {}).length !== 0
const styles = useMemo(() => {
return Object.keys(style || {}).length !== 0
? {
zIndex,
...style,
}
: {
[rtl ? 'left' : 'right']: '10px',
bottom: '20px',
[rtl ? 'left' : 'right']: pxTransform(10),
bottom: pxTransform(20),
zIndex,
}
}, [rtl, style, zIndex])

return (
<View
className={classNames(
classPrefix,
{
show: backTop,
[`${classPrefix}-show`]: backTop,
[`${classPrefix}-show-active`]: isNative && isTouchStart,
[`${classPrefix}-rn`]: rn(),
},
className
)}
style={styles}
onClick={(e) => {
goTop(e)
}}
onTouchStart={handleActiveStart}
onTouchEnd={handleActiveEnd}
onTouchCancel={handleActiveEnd}
Comment thread
lyxuncle marked this conversation as resolved.
>
{children || <Top size={19} className="nut-backtop-main" />}
</View>
Expand Down
104 changes: 59 additions & 45 deletions src/packages/backtop/backtop.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import React, { FunctionComponent, useEffect, useState, useRef } from 'react'
import React, {
FunctionComponent,
useEffect,
useState,
useRef,
useCallback,
useMemo,
} from 'react'
import type { MouseEvent } from 'react'
import { Top } from '@nutui/icons-react'
import classNames from 'classnames'
Expand Down Expand Up @@ -45,23 +52,10 @@ export const BackTop: FunctionComponent<
const classPrefix = 'nut-backtop'
const [backTop, SetBackTop] = useState(false)
const [scrollTop, SetScrollTop] = useState(0)
let startTime = 0
const startTime = useRef<number>(0)
const scrollEl: any = useRef<any>(null)
useEffect(() => {
init()
return () => removeEventListener()
}, [])

const init = () => {
if (target && document.getElementById(target)) {
scrollEl.current = document.getElementById(target) as HTMLElement | Window
} else {
scrollEl.current = window
}
addEventListener()
initCancelAniFrame()
}
const scrollListener = () => {
const scrollListener = useCallback(() => {
let top: any = null
if (scrollEl.current instanceof Window) {
top = scrollEl.current.pageYOffset
Expand All @@ -72,52 +66,71 @@ export const BackTop: FunctionComponent<
}
const showBtn = top >= threshold
SetBackTop(showBtn)
}
}, [threshold])

const addEventListener = useCallback(() => {
scrollEl.current?.addEventListener('scroll', scrollListener, false)
scrollEl.current?.addEventListener('resize', scrollListener, false)
}, [scrollListener])
Comment thread
lyxuncle marked this conversation as resolved.

const removeEventListener = useCallback(() => {
scrollEl.current?.removeEventListener('scroll', scrollListener, false)
scrollEl.current?.removeEventListener('resize', scrollListener, false)
}, [scrollListener])

const initCancelAniFrame = useCallback(() => {
window.cancelAnimationFrame = window.webkitCancelAnimationFrame
}, [])

const init = useCallback(() => {
if (target && document.getElementById(target)) {
scrollEl.current = document.getElementById(target) as HTMLElement | Window
} else {
scrollEl.current = window
}
addEventListener()
initCancelAniFrame()
}, [addEventListener, initCancelAniFrame, target])

const scroll = (y = 0) => {
useEffect(() => {
init()
return () => removeEventListener()
}, [init, removeEventListener])

const scroll = useCallback((y = 0) => {
if (scrollEl.current instanceof Window) {
window.scrollTo(0, y)
} else {
scrollEl.current.scrollTop = y
window.scrollTo(0, y)
}
}
}, [])

const scrollAnimation = () => {
const scrollAnimation = useCallback(() => {
let cid = requestAniFrame(function fn() {
const t = duration - Math.max(0, startTime - +new Date() + duration / 2)
const t =
duration - Math.max(0, startTime.current - +new Date() + duration / 2)
const y = (t * -scrollTop) / duration + scrollTop
scroll(y)
cid = requestAniFrame(fn)
if (t === duration || y === 0) {
window.cancelAnimationFrame(cid)
}
})
}
}, [duration, scroll, scrollTop, startTime])

const initCancelAniFrame = () => {
window.cancelAnimationFrame = window.webkitCancelAnimationFrame
}

function addEventListener() {
scrollEl.current?.addEventListener('scroll', scrollListener, false)
scrollEl.current?.addEventListener('resize', scrollListener, false)
}

function removeEventListener() {
scrollEl.current?.removeEventListener('scroll', scrollListener, false)
scrollEl.current?.removeEventListener('resize', scrollListener, false)
}

const goTop = (e: MouseEvent<HTMLDivElement>) => {
onClick && onClick(e)
const otime = +new Date()
startTime = otime
duration > 0 ? scrollAnimation() : scroll()
}
const goTop = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
onClick?.(e)
const otime = +new Date()
startTime.current = otime
duration > 0 ? scrollAnimation() : scroll()
},
[duration, onClick, scroll, scrollAnimation]
)

const styles =
Object.keys(style || {}).length !== 0
const styles = useMemo(() => {
return Object.keys(style || {}).length !== 0
? {
zIndex,
...style,
Expand All @@ -127,13 +140,14 @@ export const BackTop: FunctionComponent<
bottom: '20px',
zIndex,
}
}, [rtl, style, zIndex])

return (
<div
className={classNames(
classPrefix,
{
show: backTop,
[`${classPrefix}-show`]: backTop,
},
className
)}
Expand Down
Loading