折腾:
【部分解决】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; 。。。 } } } |