react实现picker单项选择滚动列表,(原生js实现)

884 阅读2分钟

最近有个需求需要使用原生js实现一个单项选择的picker,类似于

20200224161046557.png

废话不多说,直接上代码:

(其实也没什么好说的,直接看代码清楚点 ......)

// 首先的picker的方法的,事件的封装


import {
    useRef,
    useEffect,
    useCallback,
    useState
} from "react";
import { realSize } from "../../utils";
import { useMount } from "ahooks";

const limit = 80;
/** 此处用于记录每个滚动元素的高度; 注意: 你的css高度给多少,此处就是要给多少
    我使用了一个适配移动端单位的方法,此处是vw 
*/
const realHeight = realSize(30);

const usePickerMethod = (list, selectedValue) => {
    const pickerColumsEl = useRef(null);
    const startY = useRef(0);
    const lastOffset = useRef(0)
    const [selected, setSelected] = useState(selectedValue)


    // 滚动结束时判断当前选中的值
    const slectedColumnsValue = useCallback((top) => {
        let currentIndex = -(top / realHeight);
        const floor = Math.floor(currentIndex);
        if (currentIndex - floor > 0.5) {
            currentIndex = floor + 1;
        } else {
            currentIndex = floor;
        }

        const sekectedValue = list[currentIndex].label
        setSelected(sekectedValue) // 当前选中的值

        return { currentIndex }
    }, [list]);

    useEffect(() => {
        const pickerDom = pickerColumsEl.current;
        const handleTouchstart = (event) => {
            startY.current = event.targetTouches[0].pageY
        }

        const handleTouchmove = (event) => {
            const offset = event.targetTouches[0].pageY - startY.current;
            let distance = offset + lastOffset.current;

            if (distance > limit) {
                distance = limit
            }

            if (distance < -((list.length - 1) * realHeight + limit)) {
                distance = -((list.length - 1) * realHeight + limit)
            }

            pickerDom.style.transform = `translateY(${distance}px)`
        }

        const handleTouchend = (event) => {
            lastOffset.current += event.changedTouches[0].clientY - startY.current;

            if (lastOffset.current > 0) {
                lastOffset.current = 0
            }

            if (lastOffset.current < -((list.length - 1) * realHeight)) {
                lastOffset.current = -((list.length - 1) * realHeight)
            }

            const {currentIndex} = slectedColumnsValue(lastOffset.current);

            lastOffset.current = -(realHeight * currentIndex)

            pickerDom.style.transform = `translateY(${lastOffset.current}px)`
        }


        pickerDom.addEventListener('touchstart', handleTouchstart, false)
        pickerDom.addEventListener('touchmove', handleTouchmove, false)
        pickerDom.addEventListener('touchend', handleTouchend, false)

        return () => {
            pickerDom.removeEventListener('touchstart', handleTouchstart)
            pickerDom.removeEventListener('touchmove', handleTouchmove)
            pickerDom.removeEventListener('touchend', handleTouchend)
        }
    }, [list.length, slectedColumnsValue])





    // 定位到页面对应位置
    const selectPosition = () => {
        list.forEach((item, index) => {
            if (item.label === selectedValue) {
                console.log(index)
                selectIndex(index)
            }
        })
        if (!selectedValue) {
            selectIndex(0)
        }
    }
  
  // 让页面滚动到对应的index位置
    const selectIndex = (index) => {
        const pickerDom = pickerColumsEl.current;
        lastOffset.current = -(realHeight * index);
        pickerDom.style.transform = `translate3D(0, ${lastOffset.current}px, 0)`
    }


    // 仅在首次进入时才执行此方法
    useMount(() => {
        selectPosition()
    })

    return { pickerColumsEl, selected }
}

export default usePickerMethod;

其次是页面的html

import React, { useMemo } from "react";
import usePickerMethod from "./hooks";
import './index.css'

const Picker = ({ list, selectedValue }) => {
    const { pickerColumsEl, selected } = usePickerMethod(list, selectedValue)

    console.log(selected) // 滚动结束后对应的值

    const getCols = useMemo(() => {
        const result = [];
        for (let i = 0; i < list.length; i++) {
            result.push(
                <div key={i}>
                    <div className='picker-list'>
                        <span className=''>{list[i].label}</span>
                    </div>
                </div>
            )
        }
        return result;
    }, [list])


    return (
        <div className='picker-column'>
            <div className='up'></div>
            <div className='down'></div>
            <div className='picker-column-wheel' ref={pickerColumsEl}>
                {getCols}
            </div>
        </div>
    )
};

export default Picker;

当让css也很重要

.picker-column {
    height: 100%;
    user-select: none;
    touch-action: none;
    position: relative;
    overflow: hidden;
}

.up,
.down {
    position: absolute;
    left: 0;
    width: 100%;
    z-index: 10;
    height: calc(50% - 13px);
    pointer-events: none;
}

.up {
    top: -5px;
    border-bottom: 1px solid #eee;
    background: linear-gradient(0deg, hsla(0, 0%, 100%, .7) 50%, #fff);
}

.down {
    bottom: -5px;
    border-top: 1px solid #eee;
    background: linear-gradient(180deg, hsla(0, 0%, 100%, .7) 50%, #fff);
}
.picker-column-wheel{
    width: 100%;
    cursor: grab;
    position: absolute;
    top: calc(50% - 15px);
    left: 0;
    -webkit-transition: all 0.2s ease-out;
    transition: all 0.2s ease-out;
}

.picker-list {
    font-weight: bold;
    font-size: 16px;
    height: 30px;
    display: flex;
    justify-content: center;
    align-items: center;
}

Picker到这就结束了,还可以添加一些功能,比如类似antd 级联项等。
样式上Column没有做到iOS那种滚轮效果(Column看起来像个圆形的轮子一样)这个css可以后期加上
知道原理了,可以尝试着自己实现日期选择器datepicker。

  • 看完如果学会了可以点个赞哦!
  • 看完如果学会了可以点个赞哦!
  • 看完如果学会了可以点个赞哦! 重要的事情要说三遍!!!