最新消息:20210917 已从crifan.com换到crifan.org

【记录】修改后的react-mobile-datepicker的源码

ReactJS crifan 2201浏览 0评论

折腾:

【部分解决】ReactJS中react-mobile-datepicker中input被设置readonly时无法滚动选择日期

期间,虽然日期滑动问题最终证明了不是该库的问题,但是在尝试解决问题期间,修改了一些

react-mobile-datepicker的源码。

所以为了:

  • 后续更好的支持ReactJS
  • 本地没有把此库的修改后的代码保存到git仓库中

所以此处记录下来,以备后查

主要的几个改动:

1.把translateY改为了translate3d,以提高性能

2.对于touch事件,改为了ReactJS的合成事件:onTouchStart,onTouchMove,onTouchEnd,onMouseDown

3.把ReactJS中已废弃的refs改为callback式的写法

4.touchmove中去掉preventDefault -》 大大降低了滑动无效的概率 -》 现在变成刚开始用力滑动几下后,之后基本上都可以顺畅的滑动选择年月日了。

/node_modules/react-mobile-datepicker/lib/DatePickerItem.js

/**
* @module Date组件
*/
import React, { Component, PropTypes } from ‘react’;
import * as TimeUtil from ‘./time.js’;
import { shallowEqual } from ‘./pureRender.js’;
import { addPrefixCss, formatCss } from ‘./prefix.js’;
const DATE_HEIGHT = 40;                              // 每个日期的高度
const DATE_LENGTH = 10;                              // 日期的个数
const MIDDLE_INDEX = Math.floor(DATE_LENGTH / 2);     // 日期数组中间值的索引
const MIDDLE_Y = – DATE_HEIGHT * MIDDLE_INDEX;       // translateY值
/**
* Class Date组件类
* @extends Component
*/
class DatePickerItem extends Component {
    constructor(props) {
        super(props);
        this.animating = false;                 // 判断是否在transition过渡动画之中
        this.touchY = 0;                        // 保存touchstart的pageY
        this.translateY = 0;                    // 容器偏移的距离
        this.currentIndex = MIDDLE_INDEX;       // 滑动中当前日期的索引
        this.state = {
            translateY: MIDDLE_Y,
            marginTop: (this.currentIndex – MIDDLE_INDEX) * DATE_HEIGHT,
        };
        this.renderDatepickerItem = this.renderDatepickerItem.bind(this);
        this.handleContentTouch = this.handleContentTouch.bind(this);
        // this.handleContentMouseDown = this.handleContentMouseDown.bind(this);
        // this.handleContentMouseMove = this.handleContentMouseMove.bind(this);
        // this.handleContentMouseUp = this.handleContentMouseUp.bind(this);
    }
    componentWillMount() {
        this._iniDates(this.props.value);
    }
    // componentDidMount() {
    //     const viewport = this.viewport;
    //     // viewport.addEventListener(‘touchstart’, this.handleContentTouch, false);
    //     // viewport.addEventListener(‘touchmove’, this.handleContentTouch, false);
    //     // viewport.addEventListener(‘touchend’, this.handleContentTouch, false);
    //     viewport.addEventListener(‘mousedown’, this.handleContentMouseDown, false);
    // }
    componentWillReceiveProps(nextProps) {
        if (nextProps.value.getTime() === this.props.value.getTime()) {
            return;
        }
        this._iniDates(nextProps.value);
        this.currentIndex = MIDDLE_INDEX;
        this.setState({
            translateY: MIDDLE_Y,
            marginTop: (this.currentIndex – MIDDLE_INDEX) * DATE_HEIGHT,
        });
    }
    /**
     * Optimization component, Prevents unnecessary rendering
     * Only value or state change should re-rendering
     *
     * @param  {Object} nextProps next props
     * @param  {Object} nextState next state
     * @return {Boolean}          Whether re-rendering
     */
    shouldComponentUpdate(nextProps, nextState) {
        return nextProps.value.getTime() !== this.props.value.getTime() ||
            !shallowEqual(nextState, this.state);
    }
    // componentWillUnmount() {
    //     // alert(`DatePickerItem componentWillUnmount`);
    //     const viewport = this.viewport;
    //     // viewport.removeEventListener(‘touchstart’, this.handleContentTouch, false);
    //     // viewport.removeEventListener(‘touchmove’, this.handleContentTouch, false);
    //     // viewport.removeEventListener(‘touchend’, this.handleContentTouch, false);
    //     viewport.removeEventListener(‘mousedown’, this.handleContentMouseDown, false);
    // }
    _iniDates(date) {
        const { typeName } = this.props;
        const dates = Array(…Array(DATE_LENGTH))
            .map((value, index) =>
                TimeUtil[`next${typeName}`](date, index – MIDDLE_INDEX));
        this.setState({ dates });
    }
    _updateDates(direction) {
        const { typeName } = this.props;
        const { dates } = this.state;
        if (direction === 1) {
            this.currentIndex ++;
            this.setState({
                dates: [
                    …dates.slice(1),
                    TimeUtil[`next${typeName}`](dates[dates.length – 1], 1),
                ],
                marginTop: (this.currentIndex – MIDDLE_INDEX) * DATE_HEIGHT,
            });
        } else {
            this.currentIndex –;
            this.setState({
                dates: [
                    TimeUtil[`next${typeName}`](dates[0], -1),
                    …dates.slice(0, dates.length – 1),
                ],
                marginTop: (this.currentIndex – MIDDLE_INDEX) * DATE_HEIGHT,
            });
        }
    }
    _checkIsUpdateDates(direction, translateY) {
        return direction === 1 ?
            this.currentIndex * DATE_HEIGHT + DATE_HEIGHT / 2 < -translateY :
            this.currentIndex * DATE_HEIGHT – DATE_HEIGHT / 2 > -translateY;
    }
    /**
     * 清除对象的transition样式
     * @param  {Dom}     obj     指定的对象
     * @return {undefined}
     */
    _clearTransition(obj) {
        addPrefixCss(obj, { transition: ” });
    }
    /**
     * 滑动到下一日期
     * @param  {number} direction 滑动方向
     * @return {undefined}
     */
    _moveToNext(direction) {
        const date = this.state.dates[MIDDLE_INDEX];
        const { max, min } = this.props;
        if (direction === -1 && date.getTime() < min.getTime()) {
            this._updateDates(1);
        } else if (direction === 1 && date.getTime() > max.getTime()) {
            this._updateDates(-1);
        }
        // this._moveTo(this.refs.scroll, this.currentIndex);
        this._moveTo(this.scroll, this.currentIndex);
    }
    /**
     * 添加滑动动画
     * @param  {DOM} obj   DOM对象
     * @param  {number} angle 角度
     * @return {undefined}
     */
    _moveTo(obj, currentIndex) {
        this.animating = true;
        addPrefixCss(obj, { transition: ‘transform .2s ease-out’ });
        this.setState({
            translateY: -currentIndex * DATE_HEIGHT,
        });
        // NOTE: There is no transitionend, setTimeout is used instead.
        setTimeout(() => {
            this.animating = false;
            this.props.onSelect(this.state.dates[MIDDLE_INDEX]);
            // this._clearTransition(this.refs.scroll);
            this._clearTransition(this.scroll);
        }, 200);
    }
    handleStart(event) {
        // console.log(`handleStart: this.animating=${this.animating},event=${event}`);
        this.touchY = event.pageY || event.targetTouches[0].pageY;
        this.translateY = this.state.translateY;
    }
    handleMove(event) {
        // console.log(`handleMove: this.animating=${this.animating},event=${event}`);
        const touchY = event.pageY || event.targetTouches[0].pageY;
        const dir = touchY – this.touchY;
        const translateY = this.translateY + dir;
        const direction = dir > 0 ? -1 : 1;
        // 日期最小值,最大值限制
        const date = this.state.dates[MIDDLE_INDEX];
        const { max, min } = this.props;
        if (date.getTime() < min.getTime() ||
            date.getTime() > max.getTime()) {
            return;
        }
        // 检测是否更新日期列表
        if (this._checkIsUpdateDates(direction, translateY)) {
            this._updateDates(direction);
        }
        this.setState({ translateY });
    }
    handleEnd(event) {
        // console.log(`handleEnd: this.animating=${this.animating},event=${event}`);
        const touchY = event.pageY || event.changedTouches[0].pageY;
        const dir = touchY – this.touchY;
        const direction = dir > 0 ? -1 : 1;
        this._moveToNext(direction);
        // return true;
    }
    /**
     * 滑动日期选择器触屏事件
     * @param  {Object} event 事件对象
     * @return {undefined}
     */
    handleContentTouch(event) {
        // event.preventDefault();
        if (this.animating) return;
        if (event.type === ‘touchstart’) {
            event.preventDefault();
            // event.stopPropagation();
            // event.nativeEvent.stopImmediatePropagation();
            this.handleStart(event);
        } else if (event.type === ‘touchmove’) {
            this.handleMove(event);
        } else if (event.type === ‘touchend’) {
            event.preventDefault();
            // event.stopPropagation();
            // event.nativeEvent.stopImmediatePropagation();
            this.handleEnd(event);
        }
    }
    // /**
    //  * 滑动日期选择器鼠标事件
    //  * @param  {Object} event 事件对象
    //  * @return {undefined}
    //  */
    // handleContentMouseDown(event) {
    //     // console.log(`handleContentMouseDown: this.animating=${this.animating},event=${event}`);
    //     if (this.animating) return;
    //     this.handleStart(event);
    //     document.addEventListener(‘mousemove’, this.handleContentMouseMove);
    //     document.addEventListener(‘mouseup’, this.handleContentMouseUp);
    // }
    // handleContentMouseMove(event) {
    //     // console.log(`handleContentMouseMove: this.animating=${this.animating},event=${event}`);
    //     if (this.animating) return;
    //     this.handleMove(event);
    // }
    // handleContentMouseUp(event) {
    //     // console.log(`handleContentMouseUp: this.animating=${this.animating},event=${event}`);
    //     if (this.animating) return;
    //     this.handleEnd(event);
    //     document.removeEventListener(‘mousemove’, this.handleContentMouseMove);
    //     document.removeEventListener(‘mouseup’, this.handleContentMouseUp);
    // }
    /**
     * 渲染一个日期DOM对象
     * @param  {Object} date date数据
     * @return {Object}      JSX对象
     */
    renderDatepickerItem(date, index) {
        const className =
            (date < this.props.min || date > this.props.max) ?
            ‘disabled’ : ”;
        return (
            <li
                key={index}
                className={className}>
                {TimeUtil.convertDate(date, this.props.format)}
            </li>
        );
    }
                        // ref={viewport => this.viewport = viewport} // eslint-disable-line
    render() {
        const scrollStyle = formatCss({
            // transform: `translateY(${this.state.translateY}px)`,
            transform: `translate3d(0px, ${this.state.translateY}px, 0px)`,
            marginTop: this.state.marginTop,
        });
        // ref="scroll"
        // console.log(scrollStyle);
                    // onMouseDown={this.handleContentMouseDown}
        return (
            <div className="datepicker-col-1">
                <div
                    onTouchStart={this.handleContentTouch}
                    onTouchMove={this.handleContentTouch}
                    onTouchEnd={this.handleContentTouch}
                    className="datepicker-viewport">
                    <div className="datepicker-wheel">
                        <ul
                            ref={scroll => this.scroll = scroll} // eslint-disable-line
                            className="datepicker-scroll"
                            style={scrollStyle}>
                            {this.state.dates.map(this.renderDatepickerItem)}
                        </ul>
                    </div>
                </div>
            </div>
        );
    }
}
DatePickerItem.propTypes = {
    value: PropTypes.object,
    min: PropTypes.object,
    max: PropTypes.object,
    format: PropTypes.string,
    typeName: PropTypes.string,
    onSelect: PropTypes.func,
};
export default DatePickerItem;

node_modules/react-mobile-datepicker/lib/index.css

改动:

1.给.datepicker的.datepicker-scroll的li中加上:display: block;

.datepicker {
。。。
    .datepicker-scroll {
        list-style-type: none;
        &>li {
            display: block;
            。。。
        }
    }
}

转载请注明:在路上 » 【记录】修改后的react-mobile-datepicker的源码

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
99 queries in 0.197 seconds, using 23.39MB memory