diff --git a/assets/index.less b/assets/index.less index 1777d2cc8..5171b133d 100644 --- a/assets/index.less +++ b/assets/index.less @@ -27,7 +27,6 @@ padding: 5px 0; width: 100%; border-radius: @border-radius-base; - touch-action: none; .borderBox(); &-rail { @@ -58,7 +57,6 @@ border-radius: 50%; border: solid 2px tint(@primary-color, 50%); background-color: #fff; - touch-action: pan-x; &:focus { border-color: tint(@primary-color, 20%); @@ -145,6 +143,14 @@ cursor: not-allowed!important; } } + + &-dragging { + touch-action: none; + + .@{prefixClass}-handle { + touch-action: pan-x; + } + } } .@{prefixClass}-vertical { @@ -167,7 +173,6 @@ &-handle { margin-left: -5px; margin-bottom: -7px; - touch-action: pan-y; } &-mark { diff --git a/src/common/createSlider.jsx b/src/common/createSlider.jsx index b99858587..85fa40a44 100644 --- a/src/common/createSlider.jsx +++ b/src/common/createSlider.jsx @@ -125,19 +125,29 @@ export default function createSlider(Component) { if (utils.isNotTouchEvent(e)) return; const isVertical = this.props.vertical; - let position = utils.getTouchPosition(isVertical, e); + const position = utils.getTouchPosition(isVertical, e); if (!utils.isEventFromHandle(e, this.handlesRefs)) { this.dragOffset = 0; } else { const handlePosition = utils.getHandleCenterPosition(isVertical, e.target); this.dragOffset = position - handlePosition; - position = handlePosition; } - this.onStart(position); + + this.firstTouches = e.touches; + this.isDragging = true; + this.respectTouch = true; this.addDocumentTouchEvents(); utils.pauseEvent(e); } + onTouchEnd = (e) => { + if (utils.isNotTouchEvent(e)) return; + + this.firstTouches = null; + this.isDragging = false; + this.respectTouch = true; + } + onFocus = (e) => { const { onFocus, vertical } = this.props; if (utils.isEventFromHandle(e, this.handlesRefs)) { @@ -180,8 +190,20 @@ export default function createSlider(Component) { return; } - const position = utils.getTouchPosition(this.props.vertical, e); - this.onMove(e, position - this.dragOffset); + const isVertical = this.props.vertical; + const position = utils.getTouchPosition(isVertical, e); + + if (this.firstTouches) { + if (utils.isCorrectTouchDirection(this.firstTouches[0], e.touches[0], isVertical)) { + this.onStart(position - this.dragOffset); + utils.pauseEvent(e); + } else { + this.respectTouch = false; + } + this.firstTouches = null; + } else if (this.respectTouch) { + this.onMove(e, position - this.dragOffset); + } } onKeyDown = (e) => { @@ -302,6 +324,7 @@ export default function createSlider(Component) { [`${prefixCls}-with-marks`]: Object.keys(marks).length, [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-vertical`]: vertical, + [`${prefixCls}-dragging`]: this.isDragging, [className]: className, }); return ( @@ -309,6 +332,7 @@ export default function createSlider(Component) { ref={this.saveSlider} className={sliderClassName} onTouchStart={disabled ? noop : this.onTouchStart} + onTouchEnd={disabled ? noop : this.onTouchEnd} onMouseDown={disabled ? noop : this.onMouseDown} onMouseUp={disabled ? noop : this.onMouseUp} onKeyDown={disabled ? noop : this.onKeyDown} diff --git a/src/utils.js b/src/utils.js index af224a255..a4d1f0589 100644 --- a/src/utils.js +++ b/src/utils.js @@ -117,3 +117,29 @@ export function getKeyboardValueMutator(e) { default: return undefined; } } + +// http://hammerjs.github.io/api/#directions +const DIRECTION_NONE = 1; // 00001 +const DIRECTION_LEFT = 2; // 00010 +const DIRECTION_RIGHT = 4; // 00100 +const DIRECTION_UP = 8; // 01000 +const DIRECTION_DOWN = 16; // 10000 + +export function isCorrectTouchDirection(firstTouch, secondTouch, vertical) { + const x = secondTouch.clientX - firstTouch.clientX; + const y = secondTouch.clientY - firstTouch.clientY; + + let direction; + + if (x === y) { + direction = DIRECTION_NONE; + } else if (Math.abs(x) >= Math.abs(y)) { + direction = x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; + } else { + direction = y < 0 ? DIRECTION_UP : DIRECTION_DOWN; + } + + return vertical + ? (direction === DIRECTION_UP || direction === DIRECTION_DOWN) + : (direction === DIRECTION_LEFT || direction === DIRECTION_RIGHT); +} diff --git a/tests/common/createSlider.test.js b/tests/common/createSlider.test.js index 4007501e6..374c92dda 100644 --- a/tests/common/createSlider.test.js +++ b/tests/common/createSlider.test.js @@ -207,7 +207,7 @@ describe('createSlider', () => { wrapper.simulate('touchstart', { type: 'touchstart', target: leftHandle, - touches: [{ pageX: 5 }], + touches: [{ pageX: 5, clientX: 5, clientY: 0 }], stopPropagation() {}, preventDefault() {}, }); @@ -215,13 +215,43 @@ describe('createSlider', () => { wrapper.instance().onTouchMove({ // to propagation type: 'touchmove', target: leftHandle, - touches: [{ pageX: 14 }], + touches: [{ pageX: 14, clientX: 14, clientY: 0 }], stopPropagation() {}, preventDefault() {}, }); expect(wrapper.instance().getValue()).toBe(9); }); + it('should ignore TouchEvents in the wrong direction', () => { + const wrapper = mount(); + setWidth(wrapper.instance().sliderRef, 100); + const leftHandle = wrapper.find('.rc-slider-handle').at(1).instance(); + wrapper.simulate('touchstart', { + type: 'touchstart', + target: leftHandle, + touches: [{ pageX: 5, clientX: 5, clientY: 0 }], + stopPropagation() {}, + preventDefault() {}, + }); + expect(wrapper.instance().dragOffset).toBe(5); + wrapper.instance().onTouchMove({ + type: 'touchmove', + target: leftHandle, + touches: [{ pageX: 10, clientX: 10, clientY: 8 }], + stopPropagation() {}, + preventDefault() {}, + }); + expect(wrapper.instance().getValue()).toBe(0); + wrapper.instance().onTouchMove({ + type: 'touchmove', + target: leftHandle, + touches: [{ pageX: 14, clientX: 14, clientY: 8 }], + stopPropagation() {}, + preventDefault() {}, + }); + expect(wrapper.instance().getValue()).toBe(0); + }); + it('should set `dragOffset` to 0 when the TouchEvent target isn\'t a handle', () => { const wrapper = mount(); setWidth(wrapper.instance().sliderRef, 100); diff --git a/tests/utils.test.js b/tests/utils.test.js index d6da21a80..0cd71e6dd 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -47,4 +47,45 @@ describe('utils', () => { expect(utils.getClosestPoint(value, props)).toBe(96); }); }); + + describe('isCorrectTouchDirection', () => { + let vertical; + let firstTouch; + let verticalTouch; + let horizontalTouch; + + beforeEach(() => { + firstTouch = {clientX: 0, clientY: 0} + verticalTouch = {clientX: 0, clientY: 5} + horizontalTouch = {clientX: 5, clientY: 0} + }) + + describe('when vertical=FALSE', () => { + beforeEach(() => { vertical = false; }) + + it('is FALSE when secondTouch is vertically positioned from firstTouch', () => { + expect(utils.isCorrectTouchDirection(firstTouch, verticalTouch, vertical)).toBe(false); + }); + + it('is TRUE when secondTouch is horizontally positioned from firstTouch', () => { + expect(utils.isCorrectTouchDirection(firstTouch, horizontalTouch, vertical)).toBe(true); + }); + }); + + describe('when vertical=TRUE', () => { + beforeEach(() => { vertical = true; }) + + it('is TRUE when secondTouch is vertically positioned from firstTouch', () => { + expect(utils.isCorrectTouchDirection(firstTouch, verticalTouch, vertical)).toBe(true); + }); + + it('is FALSE when secondTouch is horizontally positioned from firstTouch', () => { + expect(utils.isCorrectTouchDirection(firstTouch, horizontalTouch, vertical)).toBe(false); + }); + }); + + it('is FALSE when secondTouch is identical to firstTouch', () => { + expect(utils.isCorrectTouchDirection(firstTouch, firstTouch, false)).toBe(false); + }) + }); });