One Concept Notes
To complete this component, you first need to understand several events and their parameters
Touchstart: Triggered when a finger touches the screen, even if a finger is already on the screen.
Touchmove: Triggered continuously when a finger is swiped across the screen. Call the preventDefault() event to prevent scrolling during this event.
Touchend: Triggered when the finger moves away from the screen.
We can get the required data in the callback parameter E of these three events by e.Touches [0](single finger)
- The horizontal coordinate of the pageX touch point on the page
- The vertical coordinate of the pageY touch point on the page
- The horizontal coordinate of the clientX touch point in the browser window
- The vertical coordinate of the clientY touch point in the browser window
- ScreenX The horizontal coordinate of a touch point on a screen
- ScreenY The vertical coordinate of a touch point on the screen
- Force Indicates the pressure of the touch point
- Identifier The unique identifier of the touch point
- RadiusX Horizontal radius of the touch point ellipse
- RadiusY The vertical radius of the touch point ellipse
- RotationAngle rotationAngle
Two main points
How to accurately determine the user sliding direction
According to the principle, it is only necessary to compare and judge the coordinates of Touchmove and TouchStart, but there are exceptions. For example, when the user slides left for a long distance and then suddenly slides right for a short distance, the coordinates of Touchmove are still on the left of the coordinates of TouchStart, so it is impossible to accurately judge the sliding direction.
A: In this case, we need A variable to record the amount of displacement preTouchS of the last slide
PreTouchS = TouchMove coordinates – touchStart coordinates
This variable has an initial value of 0. When sliding, you can determine the real-time sliding direction by comparing preTouchS with the current touchS
2. How to prevent excessive sliding
// Estimate displacement = initial left + touch offset
let s = touchStartLeft + touchS
// Adjust the displacement to prevent excessive slip
if (s < 0) {
/ / left smooth
s = Math.abs(s) > maxLeftS ? -maxLeftS : s
} else {
/ / right slide
s = s > 5 ? 5 : s
}
Copy the code
Where, touchStartLeft is the current offset left value recorded by each touchStart; maxLeftS is the maximum distance allowed to slide left; in the sliding deletion function, it can be set to the width of the right button group; 5 is the buffer value of the right slide
Iii Complete Code
import React, { useCallback, useEffect, useRef } from 'react'
type baseFnType = (. args:any) = > void | unknown
type RightOptionsType = {
text: string
onPress: baseFnType
style: {
backgroundColor: string
color: string}}export interface SwipeActionProps {
index: numberright? : RightOptionsType[] onOpen? : baseFnType onClose? : baseFnType disabled? :boolean
children: React.ReactNode
autoClose: boolean
}
const SwipeAction: React.FC<SwipeActionProps> = (props) = > {
const {
right: btnOptions,
index,
onOpen,
onClose,
disabled = false,
children,
autoClose = false,
} = props
const getLeft = (dom: HTMLElement): number= >
parseInt(dom.style.left || '0px'.10)
const contentDomRef = useRef<HTMLDivElement>(null)
const btnDomRef = useRef<HTMLDivElement>(null)
const touchStartX = useRef<number> (0) // Save the initial click coordinates
const touchStartLeft = useRef<number> (0) // Save the initial position when clicked
const directionRef = useRef<string> (' ') // Save the slide direction, used in both TouchMove and TouchEnd
const preTouchS = useRef<number> (0)
/ * * *@description Sliding process@param e* /
const touchmove = useCallback(
(e) = > {
if (disabled) return
const contentDom = contentDomRef.current
const startX = touchStartX.current // Start touch
const currentX = e.touches[0].pageX // Real-time location
const btnWidth = btnDomRef.current.offsetWidth // Button width
const maxLeftS = btnWidth + 15 // Slide left for maximum distance
const touchS = currentX - startX
if (touchS - preTouchS.current < 0) {
directionRef.current = 'left'
} else {
directionRef.current = 'right'
}
if (
(directionRef.current === 'right' && getLeft(contentDom) > 5) ||
(directionRef.current === 'left' && Math.abs(touchS) >= maxLeftS)
) {
return
}
// Estimate displacement = initial left + touch offset
let s = touchStartLeft.current + touchS
// Adjust the displacement to prevent excessive slip
if (s < 0) {
s = Math.abs(s) > maxLeftS ? -maxLeftS : s
} else {
s = s > 5 ? 5 : s
}
// window.requestAnimationFrame(() => {
contentDom.style.left = `${s}px`
// })
preTouchS.current = touchS
},
[disabled]
)
/ * * *@description Slide to start recording the starting position *@param e* /
const touchstart = useCallback((e) = > {
touchStartLeft.current = getLeft(contentDomRef.current)
touchStartX.current = e.touches[0].pageX
}, [])
/ * * *@description Slide end restore position *@param e* /
const touchend = useCallback(() = > {
const contentDom = contentDomRef.current
const direction = directionRef.current // Where is it going
const btnWidth = btnDomRef.current.offsetWidth // The width of the button is the maximum distance to slide
if (direction === 'left') {
contentDom.style.left = `${-btnWidth}px`
typeof onOpen === 'function' && onOpen(index)
} else if (direction === 'right') {
contentDom.style.left = `The ${0}px`
typeof onClose === 'function' && onClose(index)
}
}, [index, onClose, onOpen])
/** * restore */
const handleReset = () = > {
const contentDom = contentDomRef.current
contentDom.style.left = '0px'
}
/ * * *@description Component mounts listener/remove events */
useEffect(() = > {
const contentDom = contentDomRef.current
contentDom.style.transition = 's 0.1 all'
contentDom.addEventListener('touchstart', touchstart)
contentDom.addEventListener('touchmove', touchmove)
contentDom.addEventListener('touchend', touchend)
return () = > {
contentDom.removeEventListener('touchstart', touchstart)
contentDom.removeEventListener('touchmove', touchmove)
contentDom.removeEventListener('touchend', touchend)
}
}, [touchend, touchmove, touchstart])
return (
<div className="slider-wrap">
<div ref={contentDomRef} className="slider-content">
{children}
</div>
<div ref={btnDomRef} className="slider-btn">
{btnOptions.map((item) => (
<button
type="button"
key={item.text}
onClick={()= > {
autoClose && handleReset()
item.onPress(index)
}}
style={{
background: item.style.backgroundColor,
color: item.style.color,
}}
>
{item.text}
</button>
))}
</div>
</div>)}export default SwipeAction
Copy the code
scss
$swipeActionPrefixCls: 'slider-wrap';
$listItemPrefixCls: 'au-list-item';
.slider-wrap {
position: relative;
display: inline-block;
overflow-x: hidden; // Slide the left part off
}
.slider-content {
display: inline-block;
width: 100%;
height: 100%;
position: relative;
z-index: 20;
vertical-align: bottom; // If this is not set, wrap will be higher than content
}
.slider-btn {
position: absolute;
right: 0;
// width: 100px;
height: 100%;
display: inline-flex;
flex-direction: row;
transform: scale(0.98);
button {
flex: 1;
padding: 0;
border: none; # {}}.$swipeActionPrefixCls} {
border-top: 1px solid $gray-400;
&:last-child {
border-bottom: 1px solid $gray-400; # {}.$listItemPrefixCls} {
border: none ! important;
&:active {
opacity: 0.8; }}}Copy the code