From 6df492f5642cef23b9e988fc5a14951499ae31e4 Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Fri, 24 Jul 2020 19:45:15 +0800 Subject: [PATCH 01/13] fix the clientX and clientY of touch sensor events --- scripts/test/helpers/constants.js | 2 + src/Draggable/Plugins/Mirror/Mirror.js | 51 +------------------ .../Plugins/Scrollable/Scrollable.js | 28 +++------- .../Sensors/TouchSensor/TouchSensor.js | 18 +++---- .../TouchSensor/tests/TouchSensor.test.js | 25 +++++++++ 5 files changed, 45 insertions(+), 79 deletions(-) diff --git a/scripts/test/helpers/constants.js b/scripts/test/helpers/constants.js index dd966433..0ead6a86 100644 --- a/scripts/test/helpers/constants.js +++ b/scripts/test/helpers/constants.js @@ -1,6 +1,8 @@ export const defaultTouchEventOptions = { touches: [ { + clientX: 0, + clientY: 0, pageX: 0, pageY: 0, }, diff --git a/src/Draggable/Plugins/Mirror/Mirror.js b/src/Draggable/Plugins/Mirror/Mirror.js index 96d461ec..6e18472f 100644 --- a/src/Draggable/Plugins/Mirror/Mirror.js +++ b/src/Draggable/Plugins/Mirror/Mirror.js @@ -13,7 +13,6 @@ export const onDragMove = Symbol('onDragMove'); export const onDragStop = Symbol('onDragStop'); export const onMirrorCreated = Symbol('onMirrorCreated'); export const onMirrorMove = Symbol('onMirrorMove'); -export const onScroll = Symbol('onScroll'); export const getAppendableContainer = Symbol('getAppendableContainer'); /** @@ -67,31 +66,11 @@ export default class Mirror extends AbstractPlugin { ...this.getOptions(), }; - /** - * Scroll offset for touch devices because the mirror is positioned fixed - * @property {Object} scrollOffset - * @property {Number} scrollOffset.x - * @property {Number} scrollOffset.y - */ - this.scrollOffset = {x: 0, y: 0}; - - /** - * Initial scroll offset for touch devices because the mirror is positioned fixed - * @property {Object} scrollOffset - * @property {Number} scrollOffset.x - * @property {Number} scrollOffset.y - */ - this.initialScrollOffset = { - x: window.scrollX, - y: window.scrollY, - }; - this[onDragStart] = this[onDragStart].bind(this); this[onDragMove] = this[onDragMove].bind(this); this[onDragStop] = this[onDragStop].bind(this); this[onMirrorCreated] = this[onMirrorCreated].bind(this); this[onMirrorMove] = this[onMirrorMove].bind(this); - this[onScroll] = this[onScroll].bind(this); } /** @@ -131,15 +110,6 @@ export default class Mirror extends AbstractPlugin { return; } - if ('ontouchstart' in window) { - document.addEventListener('scroll', this[onScroll], true); - } - - this.initialScrollOffset = { - x: window.scrollX, - y: window.scrollY, - }; - const {source, originalSource, sourceContainer, sensorEvent} = dragEvent; // Last sensor position of mirror move @@ -233,13 +203,6 @@ export default class Mirror extends AbstractPlugin { } [onDragStop](dragEvent) { - if ('ontouchstart' in window) { - document.removeEventListener('scroll', this[onScroll], true); - } - - this.initialScrollOffset = {x: 0, y: 0}; - this.scrollOffset = {x: 0, y: 0}; - if (!this.mirror) { return; } @@ -261,13 +224,6 @@ export default class Mirror extends AbstractPlugin { } } - [onScroll]() { - this.scrollOffset = { - x: window.scrollX - this.initialScrollOffset.x, - y: window.scrollY - this.initialScrollOffset.y, - }; - } - /** * Mirror created handler * @param {MirrorCreatedEvent} mirrorEvent @@ -293,7 +249,6 @@ export default class Mirror extends AbstractPlugin { source, sensorEvent, mirrorClass, - scrollOffset: this.scrollOffset, options: this.options, passedThreshX: true, passedThreshY: true, @@ -337,7 +292,6 @@ export default class Mirror extends AbstractPlugin { options: this.options, initialX: this.initialX, initialY: this.initialY, - scrollOffset: this.scrollOffset, passedThreshX: mirrorEvent.passedThreshX, passedThreshY: mirrorEvent.passedThreshY, lastMovedX: this.lastMovedX, @@ -491,7 +445,6 @@ function positionMirror({withFrame = false, initial = false} = {}) { mirrorOffset, initialY, initialX, - scrollOffset, options, passedThreshX, passedThreshY, @@ -511,11 +464,11 @@ function positionMirror({withFrame = false, initial = false} = {}) { if (mirrorOffset) { const x = passedThreshX - ? Math.round((sensorEvent.clientX - mirrorOffset.left - scrollOffset.x) / (options.thresholdX || 1)) * + ? Math.round((sensorEvent.clientX - mirrorOffset.left) / (options.thresholdX || 1)) * (options.thresholdX || 1) : Math.round(lastMovedX); const y = passedThreshY - ? Math.round((sensorEvent.clientY - mirrorOffset.top - scrollOffset.y) / (options.thresholdY || 1)) * + ? Math.round((sensorEvent.clientY - mirrorOffset.top) / (options.thresholdY || 1)) * (options.thresholdY || 1) : Math.round(lastMovedY); diff --git a/src/Draggable/Plugins/Scrollable/Scrollable.js b/src/Draggable/Plugins/Scrollable/Scrollable.js index 8af279d7..dc974a98 100644 --- a/src/Draggable/Plugins/Scrollable/Scrollable.js +++ b/src/Draggable/Plugins/Scrollable/Scrollable.js @@ -49,13 +49,11 @@ export default class Scrollable extends AbstractPlugin { }; /** - * Keeps current mouse position - * @property {Object} currentMousePosition - * @property {Number} currentMousePosition.clientX - * @property {Number} currentMousePosition.clientY + * Keeps current sensor event + * @property {SensorEvent} currentSensorEvent * @type {Object|null} */ - this.currentMousePosition = null; + this.currentSensorEvent = null; /** * Scroll animation frame @@ -159,18 +157,7 @@ export default class Scrollable extends AbstractPlugin { return; } - const sensorEvent = dragEvent.sensorEvent; - const scrollOffset = {x: 0, y: 0}; - - if ('ontouchstart' in window) { - scrollOffset.y = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; - scrollOffset.x = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; - } - - this.currentMousePosition = { - clientX: sensorEvent.clientX - scrollOffset.x, - clientY: sensorEvent.clientY - scrollOffset.y, - }; + this.currentSensorEvent = dragEvent.sensorEvent; this.scrollAnimationFrame = requestAnimationFrame(this[scroll]); } @@ -186,7 +173,7 @@ export default class Scrollable extends AbstractPlugin { this.scrollableElement = null; this.scrollAnimationFrame = null; this.findScrollableElementFrame = null; - this.currentMousePosition = null; + this.currentSensorEvent = null; } /** @@ -194,7 +181,7 @@ export default class Scrollable extends AbstractPlugin { * @private */ [scroll]() { - if (!this.scrollableElement || !this.currentMousePosition) { + if (!this.scrollableElement || !this.currentSensorEvent) { return; } @@ -209,8 +196,7 @@ export default class Scrollable extends AbstractPlugin { const documentScrollingElement = getDocumentScrollingElement(); const scrollableElement = this.scrollableElement; - const clientX = this.currentMousePosition.clientX; - const clientY = this.currentMousePosition.clientY; + const {clientX, clientY} = this.currentSensorEvent; if (scrollableElement !== document.body && scrollableElement !== document.documentElement && !cutOff) { const {offsetHeight, offsetWidth} = scrollableElement; diff --git a/src/Draggable/Sensors/TouchSensor/TouchSensor.js b/src/Draggable/Sensors/TouchSensor/TouchSensor.js index 6e4bc31d..48345d28 100644 --- a/src/Draggable/Sensors/TouchSensor/TouchSensor.js +++ b/src/Draggable/Sensors/TouchSensor/TouchSensor.js @@ -140,11 +140,11 @@ export default class TouchSensor extends Sensor { [startDrag]() { const startEvent = this.startEvent; const container = this.currentContainer; - const touch = touchCoords(startEvent); + const {clientX, clientY} = touchCoords(startEvent); const dragStartEvent = new DragStartSensorEvent({ - clientX: touch.pageX, - clientY: touch.pageY, + clientX, + clientY, target: startEvent.target, container, originalEvent: startEvent, @@ -190,12 +190,12 @@ export default class TouchSensor extends Sensor { if (!this.dragging) { return; } - const {pageX, pageY} = touchCoords(event); + const {clientX, clientY, pageX, pageY} = touchCoords(event); const target = document.elementFromPoint(pageX - window.scrollX, pageY - window.scrollY); const dragMoveEvent = new DragMoveSensorEvent({ - clientX: pageX, - clientY: pageY, + clientX, + clientY, target, container: this.currentContainer, originalEvent: event, @@ -227,14 +227,14 @@ export default class TouchSensor extends Sensor { document.removeEventListener('touchmove', this[onTouchMove]); - const {pageX, pageY} = touchCoords(event); + const {clientX, clientY, pageX, pageY} = touchCoords(event); const target = document.elementFromPoint(pageX - window.scrollX, pageY - window.scrollY); event.preventDefault(); const dragStopEvent = new DragStopSensorEvent({ - clientX: pageX, - clientY: pageY, + clientX, + clientY, target, container: this.currentContainer, originalEvent: event, diff --git a/src/Draggable/Sensors/TouchSensor/tests/TouchSensor.test.js b/src/Draggable/Sensors/TouchSensor/tests/TouchSensor.test.js index a094d05f..fc12e3a1 100644 --- a/src/Draggable/Sensors/TouchSensor/tests/TouchSensor.test.js +++ b/src/Draggable/Sensors/TouchSensor/tests/TouchSensor.test.js @@ -97,6 +97,31 @@ describe('TouchSensor', () => { expect(touchEndEvent.defaultPrevented).toBe(true); }); + + it('event attributes should be set correctly', () => { + const touchEvent = { + pageX: 21, + pageY: 22, + clientX: 11, + clientY: 12, + }; + + function testAttributes(event) { + expect(event.detail.clientX).toBe(touchEvent.clientX); + expect(event.detail.clientY).toBe(touchEvent.clientY); + } + + sandbox.addEventListener('drag:start', testAttributes); + + sandbox.addEventListener('drag:move', testAttributes); + + sandbox.addEventListener('drag:stop', testAttributes); + + touchStart(draggableElement, {touches: [touchEvent]}); + waitForDragDelay(); + touchMove(draggableElement, {touches: [touchEvent]}); + touchRelease(draggableElement, {touches: [touchEvent]}); + }); }); describe('using distance', () => { From f09e1a07437aa1a1501f6b270acea857d0890973 Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Sat, 20 Jun 2020 19:45:11 +0800 Subject: [PATCH 02/13] only a demo, there are still a long way to go --- .../Plugins/SnapMirror/SnapMirror.html | 7 +++ .../Plugins/SnapMirror/SnapMirror.scss | 19 ++++++ .../src/content/Plugins/SnapMirror/index.js | 63 +++++++++++++++++++ examples/src/content/index.js | 2 + examples/src/styles/examples-app.scss | 1 + examples/src/views/data-pages.json | 3 +- examples/src/views/snap-mirror.html | 29 +++++++++ 7 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 examples/src/content/Plugins/SnapMirror/SnapMirror.html create mode 100644 examples/src/content/Plugins/SnapMirror/SnapMirror.scss create mode 100644 examples/src/content/Plugins/SnapMirror/index.js create mode 100644 examples/src/views/snap-mirror.html diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.html b/examples/src/content/Plugins/SnapMirror/SnapMirror.html new file mode 100644 index 00000000..70dde8a4 --- /dev/null +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.html @@ -0,0 +1,7 @@ +{% import 'components/Block/Block.html' as Block %} + +{% macro render(id) %} +
+
+
+{% endmacro %} diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss new file mode 100644 index 00000000..7195467e --- /dev/null +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss @@ -0,0 +1,19 @@ +//// +/// Content +/// SnapMirror +//// + +@import 'utils/shared/functions'; +@import 'utils/shared/layout'; + +.SnapMirror { + $width: 50px; + height: 500px; + background: linear-gradient(180deg, #000 0, #000 1px, transparent 0, transparent $width) 0px 0px / 100% $width repeat-y, + linear-gradient(90deg, #000 0, #000 1px, transparent 0, transparent $width) 0px 0px / #{$width} 100% repeat-x; + .box{ + width: 100px; + height: 100px; + background: aqua; + } +} diff --git a/examples/src/content/Plugins/SnapMirror/index.js b/examples/src/content/Plugins/SnapMirror/index.js new file mode 100644 index 00000000..93ed8932 --- /dev/null +++ b/examples/src/content/Plugins/SnapMirror/index.js @@ -0,0 +1,63 @@ +// eslint-disable-next-line import/no-unresolved +import {Draggable} from '@shopify/draggable'; + +function getNearestPoint(point) { + const width = 50; + const divX = parseInt(point.x / width, 10); + const divY = parseInt(point.y / width, 10); + const modX = point.x % width; + const modY = point.y % width; + + return { + x: (divX + (modX * 2 > width)) * width, + y: (divY + (modY * 2 > width)) * width, + }; +} + +export default function PluginsSnapMirror() { + const container = document.querySelector('#SnapMirror'); + + let pointerStart = {x: 0, y: 0}; + let mirrorStart = {x: 0, y: 0}; + // const offset = {x: 0, y: 0}; + + const draggable = new Draggable([container], { + draggable: '.box', + mirror: { + constrainDimensions: true, + }, + }); + + draggable.on('mirror:created', (evt) => { + const boundingClientRect = evt.source.getBoundingClientRect(); + + mirrorStart = { + x: boundingClientRect.x, + y: boundingClientRect.y, + }; + pointerStart = { + x: evt.sensorEvent.clientX, + y: evt.sensorEvent.clientY, + }; + }); + + draggable.on('mirror:move', (evt) => { + evt.cancel(); + + requestAnimationFrame(() => { + const {clientX, clientY} = evt.sensorEvent; + const nearestPoint = getNearestPoint({ + x: clientX - pointerStart.x, + y: clientY - pointerStart.y, + }); + const translate = { + x: mirrorStart.x + nearestPoint.x, + y: mirrorStart.y + nearestPoint.y, + }; + + evt.mirror.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0px)`; + }); + }); + + return draggable; +} diff --git a/examples/src/content/index.js b/examples/src/content/index.js index e6a14bcf..8f9e363d 100644 --- a/examples/src/content/index.js +++ b/examples/src/content/index.js @@ -17,6 +17,7 @@ import PluginsCollidable from './Plugins/Collidable'; import PluginsSnappable from './Plugins/Snappable'; import PluginsSwapAnimation from './Plugins/SwapAnimation'; import PluginsSortAnimation from './Plugins/SortAnimation'; +import PluginsSnapMirror from './Plugins/SnapMirror'; const Content = { Home, @@ -32,6 +33,7 @@ const Content = { PluginsSnappable, PluginsSwapAnimation, PluginsSortAnimation, + PluginsSnapMirror, }; export default Content; diff --git a/examples/src/styles/examples-app.scss b/examples/src/styles/examples-app.scss index c61ec1fe..897c982f 100644 --- a/examples/src/styles/examples-app.scss +++ b/examples/src/styles/examples-app.scss @@ -59,3 +59,4 @@ @import 'content/Plugins/Snappable/Snappable'; @import 'content/Plugins/SwapAnimation/SwapAnimation'; @import 'content/Plugins/SortAnimation/SortAnimation'; +@import 'content/Plugins/SnapMirror/SnapMirror'; diff --git a/examples/src/views/data-pages.json b/examples/src/views/data-pages.json index 0f07686f..004c79b1 100644 --- a/examples/src/views/data-pages.json +++ b/examples/src/views/data-pages.json @@ -32,7 +32,8 @@ "Collidable", "Snappable", "~Swap Animation", - "Sort Animation" + "Sort Animation", + "Snap Mirror" ] } ] diff --git a/examples/src/views/snap-mirror.html b/examples/src/views/snap-mirror.html new file mode 100644 index 00000000..18cf38f5 --- /dev/null +++ b/examples/src/views/snap-mirror.html @@ -0,0 +1,29 @@ +{% extends 'templates/document.html' %} + +{% import 'components/Document/Head.html' as Head %} +{% import 'components/Sidebar/Sidebar.html' as Sidebar %} +{% import 'components/PageHeader/PageHeader.html' as PageHeader %} + +{% import 'content/Plugins/SnapMirror/SnapMirror.html' as SnapMirror %} + +{% set ViewAttr = { + id: 'SnapMirror', + parent: 'Plugins', + child: 'Snap Mirror', + subheading: 'TODO: ' +} %} + +{% block PageId %}{{ ViewAttr.id }}{% endblock %} + +{% block head %} + {{ Head.render(ViewAttr) }} +{% endblock %} + +{% block sidebar %} + {{ Sidebar.render(ViewAttr, DataPages) }} +{% endblock %} + +{% block main %} + {{ PageHeader.render(ViewAttr) }} + {{ SnapMirror.render(ViewAttr.id) }} +{% endblock %} From eb8cb4bc889b1205b4454835be1a1e3b33a91eae Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Tue, 23 Jun 2020 21:44:53 +0800 Subject: [PATCH 03/13] add an other demo --- .../Plugins/SnapMirror/SnapMirror.html | 18 +++- .../Plugins/SnapMirror/SnapMirror.scss | 81 ++++++++++++++-- .../src/content/Plugins/SnapMirror/index.js | 92 ++++++++++++++++++- 3 files changed, 179 insertions(+), 12 deletions(-) diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.html b/examples/src/content/Plugins/SnapMirror/SnapMirror.html index 70dde8a4..a694f62b 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.html +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.html @@ -1,7 +1,21 @@ {% import 'components/Block/Block.html' as Block %} {% macro render(id) %} -
-
+
+

Grid

+
+
+

Sky

+
+
+
+
+
+
+
+
+
+
+
{% endmacro %} diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss index 7195467e..943557a1 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss @@ -7,13 +7,78 @@ @import 'utils/shared/layout'; .SnapMirror { - $width: 50px; - height: 500px; - background: linear-gradient(180deg, #000 0, #000 1px, transparent 0, transparent $width) 0px 0px / 100% $width repeat-y, - linear-gradient(90deg, #000 0, #000 1px, transparent 0, transparent $width) 0px 0px / #{$width} 100% repeat-x; - .box{ - width: 100px; - height: 100px; - background: aqua; + h2 { + font-size: 24px; + line-height: 1.5em; + } + .box { + $width: 50px; + height: 500px; + background: linear-gradient(180deg, #000 0, #000 1px, transparent 0, transparent $width) 0px 0px / 100% $width + repeat-y, + linear-gradient(90deg, #000 0, #000 1px, transparent 0, transparent $width) 0px 0px / #{$width} 100% repeat-x; + &__item { + width: 100px; + height: 100px; + background: aqua; + cursor: pointer; + } + } + .sky { + position: relative; + height: 300px; + background: #000; + &__item { + width: 10px; + height: 10px; + background: gold; + cursor: pointer; + } + .star { + position: absolute; + width: 40px; + height: 40px; + border: 1px solid gray; + &::after{ + position: absolute; + top: 50%; + left: 50%; + width: 10px; + height: 10px; + transform: translate(-50%, -50%); + background: #fff; + content: ""; + } + + } + .star1 { + left: 15%; + top: 20%; + border: none; + } + .star2 { + left: 25%; + top: 60%; + } + .star3 { + left: 35%; + top: 60%; + } + .star4 { + left: 45%; + top: 20%; + } + .star5 { + left: 55%; + top: 20%; + } + .star6 { + left: 65%; + top: 15%; + } + .star7 { + left: 75%; + top: 10%; + } } } diff --git a/examples/src/content/Plugins/SnapMirror/index.js b/examples/src/content/Plugins/SnapMirror/index.js index 93ed8932..87e60cb5 100644 --- a/examples/src/content/Plugins/SnapMirror/index.js +++ b/examples/src/content/Plugins/SnapMirror/index.js @@ -14,15 +14,100 @@ function getNearestPoint(point) { }; } +function getNearestPointForSky(point, points) { + let result = point; + let distance = Infinity; + + points.forEach((poi) => { + if ( + !( + point.x < poi.x + poi.range[1] && + point.x > poi.x - poi.range[3] && + point.y > poi.y - poi.range[0] && + point.y < poi.y + poi.range[2] + ) + ) { + return; + } + const tempDistance = (point.x - poi.x) ** 2 + (point.y - poi.y) ** 2; + if (tempDistance < distance) { + result = poi; + distance = tempDistance; + } + }); + + // console.log(points); + return result; +} + +function initSky() { + const container = document.querySelector('.sky'); + + const points = []; + let pointerStart = {x: 0, y: 0}; + let mirrorStart = {x: 0, y: 0}; + // const offset = {x: 0, y: 0}; + + const draggable = new Draggable([container], { + draggable: '.sky__item', + mirror: { + constrainDimensions: true, + }, + }); + + draggable.on('mirror:created', (evt) => { + const boundingClientRect = evt.source.getBoundingClientRect(); + + mirrorStart = { + x: boundingClientRect.x, + y: boundingClientRect.y, + }; + pointerStart = { + x: evt.sensorEvent.clientX, + y: evt.sensorEvent.clientY, + }; + + [...document.querySelectorAll('.star')].forEach((star) => { + const rect = star.getBoundingClientRect(); + let range = [15, 25, 25, 15]; + if (star.classList.contains('star1')) { + range = [Infinity, Infinity, Infinity, Infinity]; + } + points.push({x: rect.x + 15 - mirrorStart.x, y: rect.y + 15 - mirrorStart.y, range}); + }); + }); + + draggable.on('mirror:move', (evt) => { + evt.cancel(); + + requestAnimationFrame(() => { + const {clientX, clientY} = evt.sensorEvent; + const nearestPoint = getNearestPointForSky( + { + x: clientX - pointerStart.x, + y: clientY - pointerStart.y, + }, + points, + ); + const translate = { + x: mirrorStart.x + nearestPoint.x, + y: mirrorStart.y + nearestPoint.y, + }; + + evt.mirror.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0px)`; + }); + }); +} + export default function PluginsSnapMirror() { - const container = document.querySelector('#SnapMirror'); + const container = document.querySelector('.box'); let pointerStart = {x: 0, y: 0}; let mirrorStart = {x: 0, y: 0}; // const offset = {x: 0, y: 0}; const draggable = new Draggable([container], { - draggable: '.box', + draggable: '.box__item', mirror: { constrainDimensions: true, }, @@ -59,5 +144,8 @@ export default function PluginsSnapMirror() { }); }); + // demo sky + initSky(); + return draggable; } From 0434aba7941af244b8f4ba29bb52822d64ed3b63 Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Fri, 26 Jun 2020 23:02:25 +0800 Subject: [PATCH 04/13] add readme --- src/Plugins/SnapMirror/README.md | 77 ++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/Plugins/SnapMirror/README.md diff --git a/src/Plugins/SnapMirror/README.md b/src/Plugins/SnapMirror/README.md new file mode 100644 index 00000000..f93438eb --- /dev/null +++ b/src/Plugins/SnapMirror/README.md @@ -0,0 +1,77 @@ +## SnapMirror + +The SnapMirror plugin snap the mirror element to the target points. + +This plugin is not included in the default Draggable bundle, so you'll need to import it separately. + + + + + +### Import + +```js +import { Plugins } from '@shopify/draggable'; +``` + +```js +import SnapMirror from '@shopify/draggable/lib/plugins/snap-mirror'; +``` + +```html + +``` + +```html + +``` + +### Options + +**`targets {Array}`** +An object contain target options or a function returning an object contain target options. + +Target options: + +| Name | Type | Description | +| -------------- | ---------------------- | ----------- | +| `x` | `number` | | +| `y` | `number` | | +| `range` | `Object` or `Function` | | +| `range.circle` | `number` | | +| `range.rect` | `Array` | | + +**`offset {string|Object}`** +A string or an object with `x` and `y` properties. +The `offset` option lets you shift the coordinates of the targets. + +**`relativePoints {Object}`** +An object with `x` and `y` properties. +The `relativePoints` option lets you set where the drag element should snap. + +**`range {Object|Function}`** +The `range` option lets you set the default range for all targets. + +### Global Method + +**`grid(option: Object)`** +You can use the `SnapMirror.grid()` method to create a target that snaps to a grid. +The method takes an object describing a grid and returns a function that snaps to the corners of that grid. + +### Examples + +```js +import { Sortable, Plugins } from '@shopify/draggable'; + +const sortable = new Sortable(document.querySelectorAll('ul'), { + draggable: 'li', + SnapMirror: { + targets: [{x: 100, y: 100, range: 50}], + relativePoints: [{x: 0.5, y: 0.5}], + offset: "container" + }, + plugins: [Plugins.SnapMirror] +}); +``` + +### Caveats From 9f095b79c2cbfdc37358e212de0ab79ddba01c7f Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Sat, 27 Jun 2020 21:00:46 +0800 Subject: [PATCH 05/13] add grid, snap mirror plugin and test cases --- examples/.prettierrc | 5 +- .../Plugins/SnapMirror/SnapMirror.scss | 2 +- .../src/content/Plugins/SnapMirror/index.js | 150 +++--------- scripts/build/bundles.js | 7 + src/Plugins/SnapMirror/README.md | 33 +-- src/Plugins/SnapMirror/SnapMirror.js | 215 ++++++++++++++++++ src/Plugins/SnapMirror/grid.js | 23 ++ src/Plugins/SnapMirror/index.js | 4 + .../SnapMirror/tests/SnapMirror.test.js | 198 ++++++++++++++++ src/Plugins/index.js | 1 + 10 files changed, 502 insertions(+), 136 deletions(-) create mode 100644 src/Plugins/SnapMirror/SnapMirror.js create mode 100644 src/Plugins/SnapMirror/grid.js create mode 100644 src/Plugins/SnapMirror/index.js create mode 100644 src/Plugins/SnapMirror/tests/SnapMirror.test.js diff --git a/examples/.prettierrc b/examples/.prettierrc index 3f584f60..87e22d80 100644 --- a/examples/.prettierrc +++ b/examples/.prettierrc @@ -1,4 +1,7 @@ { + "trailingComma": "all", "printWidth": 120, - "singleQuote": true + "singleQuote": true, + "bracketSpacing": false, + "arrowParens": "always" } diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss index 943557a1..5b18d59a 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss @@ -49,7 +49,6 @@ background: #fff; content: ""; } - } .star1 { left: 15%; @@ -59,6 +58,7 @@ .star2 { left: 25%; top: 60%; + border-radius: 50%; } .star3 { left: 35%; diff --git a/examples/src/content/Plugins/SnapMirror/index.js b/examples/src/content/Plugins/SnapMirror/index.js index 87e60cb5..8e60dec8 100644 --- a/examples/src/content/Plugins/SnapMirror/index.js +++ b/examples/src/content/Plugins/SnapMirror/index.js @@ -1,147 +1,57 @@ // eslint-disable-next-line import/no-unresolved -import {Draggable} from '@shopify/draggable'; +import {Draggable, Plugins} from '@shopify/draggable'; -function getNearestPoint(point) { - const width = 50; - const divX = parseInt(point.x / width, 10); - const divY = parseInt(point.y / width, 10); - const modX = point.x % width; - const modY = point.y % width; - - return { - x: (divX + (modX * 2 > width)) * width, - y: (divY + (modY * 2 > width)) * width, - }; -} - -function getNearestPointForSky(point, points) { - let result = point; - let distance = Infinity; - - points.forEach((poi) => { - if ( - !( - point.x < poi.x + poi.range[1] && - point.x > poi.x - poi.range[3] && - point.y > poi.y - poi.range[0] && - point.y < poi.y + poi.range[2] - ) - ) { - return; +function initSky() { + const container = document.querySelector('.sky'); + const containerRect = container.getBoundingClientRect(); + + const targets = []; + [...document.querySelectorAll('.star')].forEach((star) => { + const rect = star.getBoundingClientRect(); + let range = {rect: [15, 25, 25, 15]}; + if (star.classList.contains('star1')) { + range = {rect: [Infinity, Infinity, Infinity, Infinity]}; } - const tempDistance = (point.x - poi.x) ** 2 + (point.y - poi.y) ** 2; - if (tempDistance < distance) { - result = poi; - distance = tempDistance; + if (star.classList.contains('star2')) { + range = {circle: 20}; } + targets.push({x: rect.x + 20 - containerRect.x, y: rect.y + 20 - containerRect.y, range}); }); - // console.log(points); - return result; -} - -function initSky() { - const container = document.querySelector('.sky'); - - const points = []; - let pointerStart = {x: 0, y: 0}; - let mirrorStart = {x: 0, y: 0}; - // const offset = {x: 0, y: 0}; - const draggable = new Draggable([container], { draggable: '.sky__item', mirror: { constrainDimensions: true, }, + plugins: [Plugins.SnapMirror], + SnapMirror: { + targets, + relativePoints: [{x: 0.5, y: 0.5}], + }, }); - draggable.on('mirror:created', (evt) => { - const boundingClientRect = evt.source.getBoundingClientRect(); - - mirrorStart = { - x: boundingClientRect.x, - y: boundingClientRect.y, - }; - pointerStart = { - x: evt.sensorEvent.clientX, - y: evt.sensorEvent.clientY, - }; - - [...document.querySelectorAll('.star')].forEach((star) => { - const rect = star.getBoundingClientRect(); - let range = [15, 25, 25, 15]; - if (star.classList.contains('star1')) { - range = [Infinity, Infinity, Infinity, Infinity]; - } - points.push({x: rect.x + 15 - mirrorStart.x, y: rect.y + 15 - mirrorStart.y, range}); - }); - }); - - draggable.on('mirror:move', (evt) => { - evt.cancel(); - - requestAnimationFrame(() => { - const {clientX, clientY} = evt.sensorEvent; - const nearestPoint = getNearestPointForSky( - { - x: clientX - pointerStart.x, - y: clientY - pointerStart.y, - }, - points, - ); - const translate = { - x: mirrorStart.x + nearestPoint.x, - y: mirrorStart.y + nearestPoint.y, - }; + draggable.on('mirror:created', () => {}); - evt.mirror.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0px)`; - }); - }); + draggable.on('mirror:move', () => {}); } export default function PluginsSnapMirror() { const container = document.querySelector('.box'); - let pointerStart = {x: 0, y: 0}; - let mirrorStart = {x: 0, y: 0}; - // const offset = {x: 0, y: 0}; - const draggable = new Draggable([container], { draggable: '.box__item', mirror: { constrainDimensions: true, }, - }); - - draggable.on('mirror:created', (evt) => { - const boundingClientRect = evt.source.getBoundingClientRect(); - - mirrorStart = { - x: boundingClientRect.x, - y: boundingClientRect.y, - }; - pointerStart = { - x: evt.sensorEvent.clientX, - y: evt.sensorEvent.clientY, - }; - }); - - draggable.on('mirror:move', (evt) => { - evt.cancel(); - - requestAnimationFrame(() => { - const {clientX, clientY} = evt.sensorEvent; - const nearestPoint = getNearestPoint({ - x: clientX - pointerStart.x, - y: clientY - pointerStart.y, - }); - const translate = { - x: mirrorStart.x + nearestPoint.x, - y: mirrorStart.y + nearestPoint.y, - }; - - evt.mirror.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0px)`; - }); + plugins: [Plugins.SnapMirror], + SnapMirror: { + targets: [ + Plugins.SnapMirror.grid({ + x: 50, + y: 50, + }), + ], + }, }); // demo sky diff --git a/scripts/build/bundles.js b/scripts/build/bundles.js index 0c9838aa..b9fce27d 100644 --- a/scripts/build/bundles.js +++ b/scripts/build/bundles.js @@ -75,6 +75,13 @@ const bundles = [ source: 'Plugins/SortAnimation/index', path: 'plugins/', }, + + { + name: 'SnapMirror', + filename: 'snap-mirror', + source: 'Plugins/SnapMirror/index', + path: 'plugins/', + }, ]; module.exports = {bundles}; diff --git a/src/Plugins/SnapMirror/README.md b/src/Plugins/SnapMirror/README.md index f93438eb..f27aea08 100644 --- a/src/Plugins/SnapMirror/README.md +++ b/src/Plugins/SnapMirror/README.md @@ -6,12 +6,12 @@ This plugin is not included in the default Draggable bundle, so you'll need to i - + ### Import ```js -import { Plugins } from '@shopify/draggable'; +import {Plugins} from '@shopify/draggable'; ``` ```js @@ -31,21 +31,23 @@ import SnapMirror from '@shopify/draggable/lib/plugins/snap-mirror'; **`targets {Array}`** An object contain target options or a function returning an object contain target options. +If a snap target is a function, then it is called and given the x and y coordinates of the event as the first two parameters and the current SnapMirror instance as the third parameter. + Target options: -| Name | Type | Description | -| -------------- | ---------------------- | ----------- | -| `x` | `number` | | -| `y` | `number` | | -| `range` | `Object` or `Function` | | -| `range.circle` | `number` | | -| `range.rect` | `Array` | | +| Name | Type | Description | +| ------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | +| `x` | `number` | The x coordinates of snap target relative to offset. | +| `y` | `number` | The y coordinates of snap target relative to offset. | +| `range` | `number` | The range of a snap target is the distance the pointer must be from the target's coordinates for a snap to be possible. | **`offset {string|Object}`** -A string or an object with `x` and `y` properties. +A string `container` or an object with `x` and `y` properties. The `offset` option lets you shift the coordinates of the targets. -**`relativePoints {Object}`** +If using `container`, offset will set to the upper left corner coordinates of the current source container. + +**`relativePoints {Array}`** An object with `x` and `y` properties. The `relativePoints` option lets you set where the drag element should snap. @@ -58,19 +60,22 @@ The `range` option lets you set the default range for all targets. You can use the `SnapMirror.grid()` method to create a target that snaps to a grid. The method takes an object describing a grid and returns a function that snaps to the corners of that grid. +**`inRectRange(range: Array)`** +You can use the `SnapMirror.rectRange()` method check if a point in ract Range. + ### Examples ```js -import { Sortable, Plugins } from '@shopify/draggable'; +import {Sortable, Plugins} from '@shopify/draggable'; const sortable = new Sortable(document.querySelectorAll('ul'), { draggable: 'li', SnapMirror: { targets: [{x: 100, y: 100, range: 50}], relativePoints: [{x: 0.5, y: 0.5}], - offset: "container" + offset: 'container', }, - plugins: [Plugins.SnapMirror] + plugins: [Plugins.SnapMirror], }); ``` diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js new file mode 100644 index 00000000..64fe9583 --- /dev/null +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -0,0 +1,215 @@ +import AbstractPlugin from 'shared/AbstractPlugin'; +import {distance as euclideanDistance} from 'shared/utils'; +import grid from './grid'; + +const onMirrorCreated = Symbol('onMirrorCreated'); +const onMirrorDestroy = Symbol('onMirrorDestroy'); +const onMirrorMove = Symbol('onMirrorMove'); + +/** + * SnapMirror default options + * @property {Object} defaultOptions + * @type {Object} + */ +export const defaultOptions = { + targets: [], + offset: 'container', + relativePoints: [ + { + x: 0, + y: 0, + }, + ], + range: Infinity, +}; + +/** + * The SnapMirror plugin snap the mirror element to the target points. + * @class SnapMirror + * @module SnapMirror + * @extends AbstractPlugin + */ +export default class SnapMirror extends AbstractPlugin { + /** + * SnapMirror constructor. + * @constructs SnapMirror + * @param {Draggable} draggable - Draggable instance + */ + constructor(draggable) { + super(draggable); + + /** + * SnapMirror options + * @property {Object} options + * @type {Object} + */ + this.options = { + ...defaultOptions, + ...this.getOptions(), + }; + + this[onMirrorCreated] = this[onMirrorCreated].bind(this); + this[onMirrorDestroy] = this[onMirrorDestroy].bind(this); + this[onMirrorMove] = this[onMirrorMove].bind(this); + } + + /** + * Attaches plugins event listeners + */ + attach() { + this.draggable.on('mirror:created', this[onMirrorCreated]).on('mirror:move', this[onMirrorMove]); + } + + /** + * Detaches plugins event listeners + */ + detach() { + this.draggable + .off('mirror:created', this[onMirrorCreated]) + .off('mirror:destroy', this[onMirrorDestroy]) + .off('mirror:move', this[onMirrorMove]); + } + + /** + * Returns options passed through draggable + * @return {Object} + */ + getOptions() { + return this.draggable.options.SnapMirror || {}; + } + + /** + * Mirror created handler + * @param {MirrorCreatedEvent} mirrorEvent + * @private + */ + [onMirrorCreated]({sourceContainer, source, sensorEvent}) { + const rect = source.getBoundingClientRect(); + this.offset = this.getOffset(sourceContainer); + + // can't get dimensions of mirror in mirror created + // so use source dimensions + this.relativePoints = this.getRelativePoints(rect); + + this.eventStartPoint = { + x: sensorEvent.clientX, + y: sensorEvent.clientY, + }; + this.startPoint = { + x: rect.x - this.offset.x, + y: rect.y - this.offset.y, + }; + } + + /** + * Mirror destroy handler + * @param {MirrorDestroyEvent} mirrorEvent + * @private + */ + [onMirrorDestroy]() { + this.offset = null; + this.relativePoints = null; + this.eventStartPoint = null; + this.startPoint = null; + } + + /** + * Drag over handler + * @param {DragOverEvent | DragOverContainer} dragEvent + * @private + */ + [onMirrorMove](evt) { + evt.cancel(); + const {clientX, clientY} = evt.sensorEvent; + + requestAnimationFrame(() => { + const nearest = this.getNearest({ + x: clientX - this.eventStartPoint.x, + y: clientY - this.eventStartPoint.y, + }); + const translate = { + x: this.offset.x + nearest.x, + y: this.offset.y + nearest.y, + }; + evt.mirror.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0)`; + }); + } + + getNearest(diff) { + let result = {x: 0, y: 0}; + let distance = Infinity; + + this.options.targets.forEach((rowTarget) => { + let target = rowTarget; + if (typeof target === 'function') { + target = target(diff.x, diff.y); + } + + const range = target.range ? target.range : this.options.range; + + this.relativePoints.forEach((relativePoint) => { + const point = { + x: this.startPoint.x + relativePoint.x, + y: this.startPoint.y + relativePoint.y, + }; + const tempPoint = { + x: point.x + diff.x, + y: point.y + diff.y, + }; + const tempDistance = euclideanDistance(tempPoint.x, tempPoint.y, target.x, target.y); + + if (tempDistance > range) { + return; + } + + if (tempDistance < distance) { + result = { + x: target.x - relativePoint.x, + y: target.y - relativePoint.y, + }; + distance = tempDistance; + } + }); + }); + + return result; + } + + getRelativePoints(rect) { + const relativePoints = []; + this.options.relativePoints.forEach((point) => { + relativePoints.push({x: rect.width * point.x, y: rect.height * point.y}); + }); + return relativePoints; + } + + getOffset(container) { + if (this.options.offset === 'container') { + const rect = container.getBoundingClientRect(); + return { + x: rect.x, + y: rect.y, + }; + } + + if (this.options.offset.x && this.options.offset.y) { + return { + x: this.options.offset.x, + y: this.options.offset.y, + }; + } + + return {x: 0, y: 0}; + } +} + +SnapMirror.grid = grid; + +SnapMirror.inRangeRange = function(coord, range) { + return ( + coord.x < this.x + range.rect[1] && + coord.x > this.x - range.rect[3] && + coord.y > this.y - range.rect[0] && + coord.y < this.y + range.rect[2] + ); +}; diff --git a/src/Plugins/SnapMirror/grid.js b/src/Plugins/SnapMirror/grid.js new file mode 100644 index 00000000..f8aea395 --- /dev/null +++ b/src/Plugins/SnapMirror/grid.js @@ -0,0 +1,23 @@ +export default function grid(gridOptions) { + const { + range, + limits = { + left: -Infinity, + right: Infinity, + top: -Infinity, + bottom: Infinity, + }, + } = gridOptions; + + return function gridFunc(x, y) { + const result = {range, grid, x: null, y: null}; + + const gridx = Math.round(x / gridOptions.x); + const gridy = Math.round(y / gridOptions.y); + + result.x = Math.max(limits.left, Math.min(limits.right, gridx * gridOptions.x)); + result.y = Math.max(limits.top, Math.min(limits.bottom, gridy * gridOptions.y)); + + return result; + }; +} diff --git a/src/Plugins/SnapMirror/index.js b/src/Plugins/SnapMirror/index.js new file mode 100644 index 00000000..85492f37 --- /dev/null +++ b/src/Plugins/SnapMirror/index.js @@ -0,0 +1,4 @@ +import SnapMirror, {defaultOptions} from './SnapMirror'; + +export default SnapMirror; +export {defaultOptions}; diff --git a/src/Plugins/SnapMirror/tests/SnapMirror.test.js b/src/Plugins/SnapMirror/tests/SnapMirror.test.js new file mode 100644 index 00000000..b70d998f --- /dev/null +++ b/src/Plugins/SnapMirror/tests/SnapMirror.test.js @@ -0,0 +1,198 @@ +import { + createSandbox, + waitForRequestAnimationFrame, + clickMouse, + moveMouse, + releaseMouse, + waitForDragDelay, + waitForPromisesToResolve, +} from 'helper'; +import {Draggable} from '../../..'; +import SnapMirror from '..'; + +const sampleMarkup = ` +
    +
  • item1
  • +
  • item2
  • +
+`; + +describe('SnapMirror', () => { + let sandbox; + let containers; + let draggable; + let draggables; + let item1; + let item2; + + beforeEach(() => { + sandbox = createSandbox(sampleMarkup); + containers = sandbox.querySelectorAll('.Container'); + draggables = sandbox.querySelectorAll('li'); + + item1 = draggables[0]; + item2 = draggables[1]; + + mockDimensions(containers[0], [0, 1000, 1000, 0]); + mockDimensions(item1, [0, 30, 30, 0]); + mockDimensions(item2, [30, 60, 60, 30]); + }); + + afterEach(() => { + draggable.destroy(); + sandbox.parentNode.removeChild(sandbox); + }); + + it('snap to targets', async () => { + draggable = new Draggable(containers, { + draggable: 'li', + plugins: [SnapMirror], + SnapMirror: { + targets: [ + {x: 100, y: 100}, + function() { + return {x: 200, y: 200}; + }, + ], + }, + }); + clickMouse(item1, {clientX: 15, clientY: 15}); + + waitForDragDelay(); + waitForRequestAnimationFrame(); + await waitForPromisesToResolve(); + + const mirror = document.querySelector('.draggable-mirror'); + + moveMouse(document.body, {clientX: 50, clientY: 10}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(100px, 100px, 0)'); + + moveMouse(document.body, {clientX: 220, clientY: 180}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(200px, 200px, 0)'); + + releaseMouse(item1); + }); + + it('offset option', async () => { + draggable = new Draggable(containers, { + draggable: 'li', + plugins: [SnapMirror], + SnapMirror: { + offset: {x: 30, y: 70}, + targets: [{x: 100, y: 100}], + }, + }); + clickMouse(item1, {clientX: 10, clientY: 10}); + + waitForDragDelay(); + waitForRequestAnimationFrame(); + await waitForPromisesToResolve(); + + const mirror = document.querySelector('.draggable-mirror'); + + moveMouse(document.body, {clientX: 50, clientY: 50}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(130px, 170px, 0)'); + + releaseMouse(item1); + }); + + it('relativePoints option', async () => { + draggable = new Draggable(containers, { + draggable: 'li', + plugins: [SnapMirror], + SnapMirror: { + relativePoints: [{x: 0.3, y: 0.7}], + targets: [{x: 100, y: 100}], + }, + }); + clickMouse(item1, {clientX: 10, clientY: 10}); + + waitForDragDelay(); + waitForRequestAnimationFrame(); + await waitForPromisesToResolve(); + + const mirror = document.querySelector('.draggable-mirror'); + + moveMouse(document.body, {clientX: 50, clientY: 50}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(91px, 79px, 0)'); + + releaseMouse(item1); + }); + + it('SnapMirror.grid', async () => { + draggable = new Draggable(containers, { + draggable: 'li', + plugins: [SnapMirror], + SnapMirror: { + targets: [SnapMirror.grid({x: 50, y: 50})], + }, + }); + clickMouse(item1, {clientX: 10, clientY: 10}); + + waitForDragDelay(); + waitForRequestAnimationFrame(); + await waitForPromisesToResolve(); + + const mirror = document.querySelector('.draggable-mirror'); + + moveMouse(document.body, {clientX: 20, clientY: 10}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(0px, 0px, 0)'); + + moveMouse(document.body, {clientX: 60, clientY: 10}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(50px, 0px, 0)'); + + moveMouse(document.body, {clientX: 40, clientY: 40}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(50px, 50px, 0)'); + + moveMouse(document.body, {clientX: 440, clientY: 550}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(450px, 550px, 0)'); + + releaseMouse(item1); + }); +}); + +function mockDimensions(element, [top, right, bottom, left]) { + const width = right - left; + const height = bottom - top; + Object.assign(element.style, { + width: `${width}px`, + height: `${height}px`, + }); + + element.getBoundingClientRect = () => ({ + width, + height, + top, + right, + bottom, + left, + x: top, + y: left, + }); + + element.cloneNode = function(...args) { + const node = Node.prototype.cloneNode.apply(element, args); + node.getBoundingClientRect = function() { + return element.getBoundingClientRect(); + }; + return node; + }; + + return element; +} diff --git a/src/Plugins/index.js b/src/Plugins/index.js index 30da0fb8..b2c45dc2 100644 --- a/src/Plugins/index.js +++ b/src/Plugins/index.js @@ -3,3 +3,4 @@ export {default as ResizeMirror, defaultOptions as defaultResizeMirrorOptions} f export {default as Snappable} from './Snappable'; export {default as SwapAnimation, defaultOptions as defaultSwapAnimationOptions} from './SwapAnimation'; export {default as SortAnimation, defaultOptions as defaultSortAnimationOptions} from './SortAnimation'; +export {default as SnapMirror, defaultOptions as defaultSnapMirrorOptions} from './SnapMirror'; From 2d99ba0dfe26cbb8d3c0470f89fab2a293491447 Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Mon, 6 Jul 2020 23:12:20 +0800 Subject: [PATCH 06/13] This impl seems not suitable for draggable, will redesign --- src/Plugins/SnapMirror/SnapMirror.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js index 64fe9583..cda3a7c1 100644 --- a/src/Plugins/SnapMirror/SnapMirror.js +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -83,7 +83,7 @@ export default class SnapMirror extends AbstractPlugin { * @param {MirrorCreatedEvent} mirrorEvent * @private */ - [onMirrorCreated]({sourceContainer, source, sensorEvent}) { + [onMirrorCreated]({sourceContainer, source, originalEvent}) { const rect = source.getBoundingClientRect(); this.offset = this.getOffset(sourceContainer); @@ -92,8 +92,8 @@ export default class SnapMirror extends AbstractPlugin { this.relativePoints = this.getRelativePoints(rect); this.eventStartPoint = { - x: sensorEvent.clientX, - y: sensorEvent.clientY, + x: originalEvent.pageX, + y: originalEvent.pageY, }; this.startPoint = { x: rect.x - this.offset.x, @@ -120,12 +120,11 @@ export default class SnapMirror extends AbstractPlugin { */ [onMirrorMove](evt) { evt.cancel(); - const {clientX, clientY} = evt.sensorEvent; - + const {pageX, pageY} = evt.originalEvent; requestAnimationFrame(() => { const nearest = this.getNearest({ - x: clientX - this.eventStartPoint.x, - y: clientY - this.eventStartPoint.y, + x: pageX - this.eventStartPoint.x, + y: pageY - this.eventStartPoint.y, }); const translate = { x: this.offset.x + nearest.x, From dbe6f8268b07691edbd128b6cf904b9406e8f27c Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Tue, 7 Jul 2020 18:44:54 +0800 Subject: [PATCH 07/13] update snap mirror to position mirror coordinate with over container --- .../Plugins/SnapMirror/SnapMirror.scss | 7 +- src/Plugins/SnapMirror/README.md | 20 +++- src/Plugins/SnapMirror/SnapMirror.js | 102 ++++++++++-------- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss index 5b18d59a..52af3854 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss @@ -7,6 +7,9 @@ @import 'utils/shared/layout'; .SnapMirror { + .draggable-container--over { + position: relative; + } h2 { font-size: 24px; line-height: 1.5em; @@ -39,7 +42,7 @@ width: 40px; height: 40px; border: 1px solid gray; - &::after{ + &::after { position: absolute; top: 50%; left: 50%; @@ -47,7 +50,7 @@ height: 10px; transform: translate(-50%, -50%); background: #fff; - content: ""; + content: ''; } } .star1 { diff --git a/src/Plugins/SnapMirror/README.md b/src/Plugins/SnapMirror/README.md index f27aea08..64bf8752 100644 --- a/src/Plugins/SnapMirror/README.md +++ b/src/Plugins/SnapMirror/README.md @@ -26,6 +26,14 @@ import SnapMirror from '@shopify/draggable/lib/plugins/snap-mirror'; ``` +The over container should set relative/absolute/fixed position, bacause while over a container mirror using absolute position based on the container. + +```css +.draggable-container--over { + position: relative; +} +``` + ### Options **`targets {Array}`** @@ -41,11 +49,14 @@ Target options: | `y` | `number` | The y coordinates of snap target relative to offset. | | `range` | `number` | The range of a snap target is the distance the pointer must be from the target's coordinates for a snap to be possible. | -**`offset {string|Object}`** -A string `container` or an object with `x` and `y` properties. + **`relativePoints {Array}`** An object with `x` and `y` properties. @@ -73,10 +84,13 @@ const sortable = new Sortable(document.querySelectorAll('ul'), { SnapMirror: { targets: [{x: 100, y: 100, range: 50}], relativePoints: [{x: 0.5, y: 0.5}], - offset: 'container', }, plugins: [Plugins.SnapMirror], }); ``` +# Why different form interact.js + +Consider of scorll, nest container and nest container with scorll. Limit snap in a contianer will make things simple. + ### Caveats diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js index cda3a7c1..d1e1cff6 100644 --- a/src/Plugins/SnapMirror/SnapMirror.js +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -5,6 +5,8 @@ import grid from './grid'; const onMirrorCreated = Symbol('onMirrorCreated'); const onMirrorDestroy = Symbol('onMirrorDestroy'); const onMirrorMove = Symbol('onMirrorMove'); +const onDragOverContainer = Symbol('onDragOverContainer'); +const onDragOutContainer = Symbol('onDragOutContainer'); /** * SnapMirror default options @@ -13,7 +15,6 @@ const onMirrorMove = Symbol('onMirrorMove'); */ export const defaultOptions = { targets: [], - offset: 'container', relativePoints: [ { x: 0, @@ -51,13 +52,20 @@ export default class SnapMirror extends AbstractPlugin { this[onMirrorCreated] = this[onMirrorCreated].bind(this); this[onMirrorDestroy] = this[onMirrorDestroy].bind(this); this[onMirrorMove] = this[onMirrorMove].bind(this); + this[onDragOverContainer] = this[onDragOverContainer].bind(this); + this[onDragOutContainer] = this[onDragOutContainer].bind(this); } /** * Attaches plugins event listeners */ attach() { - this.draggable.on('mirror:created', this[onMirrorCreated]).on('mirror:move', this[onMirrorMove]); + this.draggable + .on('mirror:created', this[onMirrorCreated]) + .on('mirror:move', this[onMirrorMove]) + .on('drag:over:container', this[onDragOverContainer]) + .on('drag:out:container', this[onDragOutContainer]) + .on('mirror:destroy', this[onMirrorDestroy]); } /** @@ -66,8 +74,10 @@ export default class SnapMirror extends AbstractPlugin { detach() { this.draggable .off('mirror:created', this[onMirrorCreated]) - .off('mirror:destroy', this[onMirrorDestroy]) - .off('mirror:move', this[onMirrorMove]); + .off('drag:over:container', this[onDragOverContainer]) + .off('drag:out:container', this[onDragOutContainer]) + .off('mirror:move', this[onMirrorMove]) + .off('mirror:destroy', this[onMirrorDestroy]); } /** @@ -83,22 +93,19 @@ export default class SnapMirror extends AbstractPlugin { * @param {MirrorCreatedEvent} mirrorEvent * @private */ - [onMirrorCreated]({sourceContainer, source, originalEvent}) { - const rect = source.getBoundingClientRect(); - this.offset = this.getOffset(sourceContainer); + [onMirrorCreated](evt) { + const rect = evt.source.getBoundingClientRect(); // can't get dimensions of mirror in mirror created // so use source dimensions - this.relativePoints = this.getRelativePoints(rect); + this.relativePoints = this.getRelativePoints(rect, evt.originalEvent); - this.eventStartPoint = { - x: originalEvent.pageX, - y: originalEvent.pageY, - }; - this.startPoint = { - x: rect.x - this.offset.x, - y: rect.y - this.offset.y, + this.offset = { + x: rect.x - evt.sensorEvent.clientX, + y: rect.y - evt.sensorEvent.clientY, }; + + this.mirror = evt.mirror; } /** @@ -109,7 +116,6 @@ export default class SnapMirror extends AbstractPlugin { [onMirrorDestroy]() { this.offset = null; this.relativePoints = null; - this.eventStartPoint = null; this.startPoint = null; } @@ -119,21 +125,40 @@ export default class SnapMirror extends AbstractPlugin { * @private */ [onMirrorMove](evt) { + if (!this.overContainer) { + return; + } evt.cancel(); - const {pageX, pageY} = evt.originalEvent; requestAnimationFrame(() => { + // console.log(evt.originalEvent.pageY, evt.originalEvent.pageX); + // console.log(this.overContainer.scrollTop, this.overContainer.scrollLeft); + // console.log(this.overContainer.offsetTop, this.overContainer.offsetLeft); const nearest = this.getNearest({ - x: pageX - this.eventStartPoint.x, - y: pageY - this.eventStartPoint.y, + x: evt.originalEvent.pageX + this.overContainer.scrollLeft - this.overContainer.offsetLeft, + y: evt.originalEvent.pageY + this.overContainer.scrollTop - this.overContainer.offsetTop, }); - const translate = { - x: this.offset.x + nearest.x, - y: this.offset.y + nearest.y, - }; - evt.mirror.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0)`; + // console.log(nearest); + this.mirror.style.transform = null; + evt.mirror.style.left = `${nearest.x}px`; + evt.mirror.style.top = `${nearest.y}px`; }); } + [onDragOverContainer](evt) { + this.overContainer = evt.overContainer; + this.overContainer.append(this.mirror); + this.mirror.style.position = 'absolute'; + } + + [onDragOutContainer](evt) { + this.overContainer.position = null; + this.overContainer = null; + evt.sourceContainer.append(this.mirror); + this.mirror.style.position = 'fixed'; + this.mirror.style.top = '0'; + this.mirror.style.left = '0'; + } + getNearest(diff) { let result = {x: 0, y: 0}; let distance = Infinity; @@ -147,13 +172,9 @@ export default class SnapMirror extends AbstractPlugin { const range = target.range ? target.range : this.options.range; this.relativePoints.forEach((relativePoint) => { - const point = { - x: this.startPoint.x + relativePoint.x, - y: this.startPoint.y + relativePoint.y, - }; const tempPoint = { - x: point.x + diff.x, - y: point.y + diff.y, + x: diff.x + this.offset.x + relativePoint.x, + y: diff.y + this.offset.y + relativePoint.y, }; const tempDistance = euclideanDistance(tempPoint.x, tempPoint.y, target.x, target.y); @@ -183,22 +204,11 @@ export default class SnapMirror extends AbstractPlugin { } getOffset(container) { - if (this.options.offset === 'container') { - const rect = container.getBoundingClientRect(); - return { - x: rect.x, - y: rect.y, - }; - } - - if (this.options.offset.x && this.options.offset.y) { - return { - x: this.options.offset.x, - y: this.options.offset.y, - }; - } - - return {x: 0, y: 0}; + const rect = container.getBoundingClientRect(); + return { + x: rect.x, + y: rect.y, + }; } } From 03463685497ea2735947501b854e9fe039b709e4 Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Wed, 8 Jul 2020 22:29:33 +0800 Subject: [PATCH 08/13] update plugin, examples and tests --- .../Plugins/SnapMirror/SnapMirror.html | 41 +++-- .../Plugins/SnapMirror/SnapMirror.scss | 156 ++++++++++-------- .../src/content/Plugins/SnapMirror/index.js | 34 ++-- examples/src/views/snap-mirror.html | 10 +- src/Plugins/SnapMirror/README.md | 14 +- src/Plugins/SnapMirror/SnapMirror.js | 42 ++--- .../SnapMirror/tests/SnapMirror.test.js | 70 ++++---- 7 files changed, 201 insertions(+), 166 deletions(-) diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.html b/examples/src/content/Plugins/SnapMirror/SnapMirror.html index a694f62b..a1ded72a 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.html +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.html @@ -2,20 +2,31 @@ {% macro render(id) %}
-

Grid

-
-
-
-

Sky

-
-
-
-
-
-
-
-
-
-
+
+
+ {{ Block.render('drag', {index: 1, draggable: true}) }} +
+
+ +
+ +
+
+ {{ Block.render('1', {index: 1, draggable: true}) }} + {{ Block.render('2', {index: 2, draggable: true}) }} + {{ Block.render('3', {index: 3, draggable: true}) }} + {{ Block.render('4', {index: 4, draggable: true}) }} + {{ Block.render('5', {index: 5, draggable: true}) }} + {{ Block.render('6', {index: 6, draggable: true}) }} +
+
+ {{ Block.render('', {type: 'Hollow'}) }} + {{ Block.render('', {type: 'Hollow'}) }} + {{ Block.render('', {type: 'Hollow'}) }} + {{ Block.render('', {type: 'Hollow'}) }} + {{ Block.render('', {type: 'Hollow'}) }} + {{ Block.render('', {type: 'Hollow'}) }} +
+
{% endmacro %} diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss index 52af3854..c1f6b00b 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss @@ -7,81 +7,93 @@ @import 'utils/shared/layout'; .SnapMirror { - .draggable-container--over { - position: relative; - } - h2 { - font-size: 24px; - line-height: 1.5em; - } - .box { - $width: 50px; - height: 500px; - background: linear-gradient(180deg, #000 0, #000 1px, transparent 0, transparent $width) 0px 0px / 100% $width - repeat-y, - linear-gradient(90deg, #000 0, #000 1px, transparent 0, transparent $width) 0px 0px / #{$width} 100% repeat-x; - &__item { - width: 100px; - height: 100px; - background: aqua; - cursor: pointer; - } - } - .sky { - position: relative; - height: 300px; - background: #000; - &__item { - width: 10px; - height: 10px; - background: gold; - cursor: pointer; - } - .star { - position: absolute; - width: 40px; - height: 40px; - border: 1px solid gray; - &::after { - position: absolute; - top: 50%; - left: 50%; - width: 10px; - height: 10px; - transform: translate(-50%, -50%); - background: #fff; - content: ''; + .Workspace { + &.BlockLayout--typePositioned { + height: 32rem; + border: 0.6rem solid #212529; + overflow: auto; + + .Block { + width: 150px; + height: 150px; + } + &.draggable-container--over { + .Workspace__grid { + $size: 50px; + $border-width: 0.3rem; + background: linear-gradient(180deg, #00f 0, #00f $border-width, transparent 0, transparent $size) 0px 0px / + 100% $size repeat-y, + linear-gradient(90deg, #00f 0, #00f $border-width, transparent 0, transparent $size) 0px 0px / #{$size} 100% + repeat-x; + } } } - .star1 { - left: 15%; - top: 20%; - border: none; - } - .star2 { - left: 25%; - top: 60%; - border-radius: 50%; - } - .star3 { - left: 35%; - top: 60%; - } - .star4 { - left: 45%; - top: 20%; - } - .star5 { - left: 55%; - top: 20%; - } - .star6 { - left: 65%; - top: 15%; + + &__grid { + $size: 50px; + $border-width: 0.3rem; + width: 1500px; + height: 1000px; + background: linear-gradient(180deg, #000 0, #000 $border-width, transparent 0, transparent $size) 0px 0px / 100% + $size repeat-y, + linear-gradient(90deg, #000 0, #000 $border-width, transparent 0, transparent $size) 0px 0px / #{$size} 100% repeat-x; } - .star7 { - left: 75%; - top: 10%; + } + + .MLP { + &.BlockLayout--typePositioned { + height: 64rem; + border: 0.6rem solid #212529; + overflow: auto; + .Block-wrapper { + display: flex; + .Block { + width: 50px; + height: 50px; + } + .Block:nth-child(1) { + } + .Block:nth-child(2) { + } + .Block:nth-child(3) { + } + .Block:nth-child(4) { + } + .Block:nth-child(5) { + } + .Block:nth-child(6) { + } + } + + .Hollow-wrapper { + position: relative; + height: 500px; + .Block--typeHollow { + width: 50px; + height: 50px; + position: absolute; + top: 50%; + left: 50%; + } + .Block--typeHollow:nth-last-child(1) { + transform: rotate(60deg) translateX(200px) rotate(-60deg); + } + .Block--typeHollow:nth-last-child(2) { + transform: rotate(120deg) translateX(200px) rotate(-120deg); + } + .Block--typeHollow:nth-last-child(3) { + transform: rotate(180deg) translateX(200px) rotate(-180deg); + } + .Block--typeHollow:nth-last-child(4) { + transform: rotate(240deg) translateX(200px) rotate(-240deg); + } + .Block--typeHollow:nth-last-child(5) { + transform: rotate(300deg) translateX(200px) rotate(-300deg); + } + .Block--typeHollow:nth-last-child(6) { + transform: rotate(360deg) translateX(200px) rotate(-360deg); + } + } } } } diff --git a/examples/src/content/Plugins/SnapMirror/index.js b/examples/src/content/Plugins/SnapMirror/index.js index 8e60dec8..16ca0f31 100644 --- a/examples/src/content/Plugins/SnapMirror/index.js +++ b/examples/src/content/Plugins/SnapMirror/index.js @@ -1,25 +1,25 @@ // eslint-disable-next-line import/no-unresolved import {Draggable, Plugins} from '@shopify/draggable'; -function initSky() { - const container = document.querySelector('.sky'); +function initMLP() { + const container = document.querySelector('#SnapMirror .BlockLayout.MLP'); const containerRect = container.getBoundingClientRect(); const targets = []; - [...document.querySelectorAll('.star')].forEach((star) => { + [...document.querySelectorAll('.Block--typeHollow')].forEach((star) => { const rect = star.getBoundingClientRect(); - let range = {rect: [15, 25, 25, 15]}; + let range = Plugins.SnapMirror.inRectRange([15, 25, 25, 15]); if (star.classList.contains('star1')) { - range = {rect: [Infinity, Infinity, Infinity, Infinity]}; + range = Plugins.SnapMirror.inRectRange([Infinity, Infinity, Infinity, Infinity]); } if (star.classList.contains('star2')) { - range = {circle: 20}; + range = 20; } targets.push({x: rect.x + 20 - containerRect.x, y: rect.y + 20 - containerRect.y, range}); }); const draggable = new Draggable([container], { - draggable: '.sky__item', + draggable: '.Block--isDraggable', mirror: { constrainDimensions: true, }, @@ -30,16 +30,14 @@ function initSky() { }, }); - draggable.on('mirror:created', () => {}); - - draggable.on('mirror:move', () => {}); + return draggable; } -export default function PluginsSnapMirror() { - const container = document.querySelector('.box'); +function initWorkspace() { + const container = document.querySelector('#SnapMirror .BlockLayout.Workspace'); const draggable = new Draggable([container], { - draggable: '.box__item', + draggable: '.Block--isDraggable', mirror: { constrainDimensions: true, }, @@ -54,8 +52,12 @@ export default function PluginsSnapMirror() { }, }); - // demo sky - initSky(); - return draggable; } + +export default function PluginsSnapMirror() { + const workspaceDraggable = initWorkspace(); + const MLPDraggable = initMLP(); + + return [workspaceDraggable, MLPDraggable]; +} diff --git a/examples/src/views/snap-mirror.html b/examples/src/views/snap-mirror.html index 18cf38f5..0e0e2ed9 100644 --- a/examples/src/views/snap-mirror.html +++ b/examples/src/views/snap-mirror.html @@ -10,20 +10,20 @@ id: 'SnapMirror', parent: 'Plugins', child: 'Snap Mirror', - subheading: 'TODO: ' + subheading: 'Enable snap mirror to target points by including the SnapMirror plugin. Drag an item into the range of a target point will snap to the point.' } %} {% block PageId %}{{ ViewAttr.id }}{% endblock %} {% block head %} - {{ Head.render(ViewAttr) }} +{{ Head.render(ViewAttr) }} {% endblock %} {% block sidebar %} - {{ Sidebar.render(ViewAttr, DataPages) }} +{{ Sidebar.render(ViewAttr, DataPages) }} {% endblock %} {% block main %} - {{ PageHeader.render(ViewAttr) }} - {{ SnapMirror.render(ViewAttr.id) }} +{{ PageHeader.render(ViewAttr) }} +{{ SnapMirror.render(ViewAttr.id) }} {% endblock %} diff --git a/src/Plugins/SnapMirror/README.md b/src/Plugins/SnapMirror/README.md index 64bf8752..85996f47 100644 --- a/src/Plugins/SnapMirror/README.md +++ b/src/Plugins/SnapMirror/README.md @@ -1,11 +1,9 @@ ## SnapMirror -The SnapMirror plugin snap the mirror element to the target points. +The SnapMirror plugin snap the mirror to the target points. This plugin is not included in the default Draggable bundle, so you'll need to import it separately. - - ### Import @@ -39,7 +37,7 @@ The over container should set relative/absolute/fixed position, bacause while ov **`targets {Array}`** An object contain target options or a function returning an object contain target options. -If a snap target is a function, then it is called and given the x and y coordinates of the event as the first two parameters and the current SnapMirror instance as the third parameter. +If a snap target is a function, then it is called and given the x and y coordinates of the dragging mirror base on contianer as the first two parameters and the current SnapMirror instance as the third parameter. Target options: @@ -60,7 +58,7 @@ If using `container`, offset will set to the upper left corner coordinates of th **`relativePoints {Array}`** An object with `x` and `y` properties. -The `relativePoints` option lets you set where the drag element should snap. +The `relativePoints` option lets you set where the dragging mirror element should snap. **`range {Object|Function}`** The `range` option lets you set the default range for all targets. @@ -89,8 +87,8 @@ const sortable = new Sortable(document.querySelectorAll('ul'), { }); ``` -# Why different form interact.js +### Caveats -Consider of scorll, nest container and nest container with scorll. Limit snap in a contianer will make things simple. +### Why different form interact.js -### Caveats +Consider of scorll, nest container and nest container with scorll. Limit snap in a contianer will make things simple. diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js index d1e1cff6..755922fe 100644 --- a/src/Plugins/SnapMirror/SnapMirror.js +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -130,17 +130,19 @@ export default class SnapMirror extends AbstractPlugin { } evt.cancel(); requestAnimationFrame(() => { + if (!this.overContainer) { + return; + } // console.log(evt.originalEvent.pageY, evt.originalEvent.pageX); // console.log(this.overContainer.scrollTop, this.overContainer.scrollLeft); // console.log(this.overContainer.offsetTop, this.overContainer.offsetLeft); const nearest = this.getNearest({ - x: evt.originalEvent.pageX + this.overContainer.scrollLeft - this.overContainer.offsetLeft, - y: evt.originalEvent.pageY + this.overContainer.scrollTop - this.overContainer.offsetTop, + // currentPageX - contianerOffset + contanierScroll + selfOffset + x: evt.originalEvent.pageX + this.overContainer.scrollLeft - this.overContainer.offsetLeft + this.offset.x, + y: evt.originalEvent.pageY + this.overContainer.scrollTop - this.overContainer.offsetTop + this.offset.y, }); // console.log(nearest); - this.mirror.style.transform = null; - evt.mirror.style.left = `${nearest.x}px`; - evt.mirror.style.top = `${nearest.y}px`; + this.mirror.style.transform = `translate3d(${nearest.x}px, ${nearest.y}px, 0)`; }); } @@ -155,30 +157,28 @@ export default class SnapMirror extends AbstractPlugin { this.overContainer = null; evt.sourceContainer.append(this.mirror); this.mirror.style.position = 'fixed'; - this.mirror.style.top = '0'; - this.mirror.style.left = '0'; } - getNearest(diff) { - let result = {x: 0, y: 0}; + getNearest(coord) { + let result = {x: coord.x, y: coord.y}; let distance = Infinity; this.options.targets.forEach((rowTarget) => { let target = rowTarget; if (typeof target === 'function') { - target = target(diff.x, diff.y); + target = target(coord.x, coord.y, this); } const range = target.range ? target.range : this.options.range; this.relativePoints.forEach((relativePoint) => { const tempPoint = { - x: diff.x + this.offset.x + relativePoint.x, - y: diff.y + this.offset.y + relativePoint.y, + x: coord.x + relativePoint.x, + y: coord.y + relativePoint.y, }; const tempDistance = euclideanDistance(tempPoint.x, tempPoint.y, target.x, target.y); - if (tempDistance > range) { + if ((typeof range === 'function' && !range(target, tempPoint)) || tempDistance > range) { return; } @@ -214,11 +214,13 @@ export default class SnapMirror extends AbstractPlugin { SnapMirror.grid = grid; -SnapMirror.inRangeRange = function(coord, range) { - return ( - coord.x < this.x + range.rect[1] && - coord.x > this.x - range.rect[3] && - coord.y > this.y - range.rect[0] && - coord.y < this.y + range.rect[2] - ); +SnapMirror.inRectRange = function(range) { + return function(target, coord) { + return ( + coord.x < target.x + range[1] && + coord.x > target.x - range[3] && + coord.y > target.y - range[0] && + coord.y < target.y + range[2] + ); + }; }; diff --git a/src/Plugins/SnapMirror/tests/SnapMirror.test.js b/src/Plugins/SnapMirror/tests/SnapMirror.test.js index b70d998f..227e4943 100644 --- a/src/Plugins/SnapMirror/tests/SnapMirror.test.js +++ b/src/Plugins/SnapMirror/tests/SnapMirror.test.js @@ -43,7 +43,7 @@ describe('SnapMirror', () => { sandbox.parentNode.removeChild(sandbox); }); - it('snap to targets', async () => { + it('targets option', async () => { draggable = new Draggable(containers, { draggable: 'li', plugins: [SnapMirror], @@ -56,20 +56,19 @@ describe('SnapMirror', () => { ], }, }); - clickMouse(item1, {clientX: 15, clientY: 15}); + clickMouse(item1, {pageX: 15, pageY: 15}); waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); - const mirror = document.querySelector('.draggable-mirror'); - moveMouse(document.body, {clientX: 50, clientY: 10}); + moveMouse(item1, {pageX: 50, pageY: 10}); await waitForPromisesToResolve(); waitForRequestAnimationFrame(); expect(mirror.style.transform).toBe('translate3d(100px, 100px, 0)'); - moveMouse(document.body, {clientX: 220, clientY: 180}); + moveMouse(item1, {pageX: 220, pageY: 180}); await waitForPromisesToResolve(); waitForRequestAnimationFrame(); expect(mirror.style.transform).toBe('translate3d(200px, 200px, 0)'); @@ -77,91 +76,102 @@ describe('SnapMirror', () => { releaseMouse(item1); }); - it('offset option', async () => { + it('relativePoints option', async () => { draggable = new Draggable(containers, { draggable: 'li', plugins: [SnapMirror], SnapMirror: { - offset: {x: 30, y: 70}, + relativePoints: [{x: 0.3, y: 0.7}], targets: [{x: 100, y: 100}], }, }); - clickMouse(item1, {clientX: 10, clientY: 10}); + clickMouse(item1, {pageX: 10, pageY: 10}); waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); - const mirror = document.querySelector('.draggable-mirror'); - moveMouse(document.body, {clientX: 50, clientY: 50}); + moveMouse(item1, {pageX: 50, pageY: 50}); await waitForPromisesToResolve(); waitForRequestAnimationFrame(); - expect(mirror.style.transform).toBe('translate3d(130px, 170px, 0)'); + expect(mirror.style.transform).toBe('translate3d(91px, 79px, 0)'); releaseMouse(item1); }); - it('relativePoints option', async () => { + it('SnapMirror.grid()', async () => { draggable = new Draggable(containers, { draggable: 'li', plugins: [SnapMirror], SnapMirror: { - relativePoints: [{x: 0.3, y: 0.7}], - targets: [{x: 100, y: 100}], + targets: [SnapMirror.grid({x: 50, y: 50})], }, }); - clickMouse(item1, {clientX: 10, clientY: 10}); + clickMouse(item1, {pageX: 10, pageY: 10}); waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); - const mirror = document.querySelector('.draggable-mirror'); - moveMouse(document.body, {clientX: 50, clientY: 50}); + moveMouse(item1, {pageX: 20, pageY: 10}); await waitForPromisesToResolve(); waitForRequestAnimationFrame(); - expect(mirror.style.transform).toBe('translate3d(91px, 79px, 0)'); + expect(mirror.style.transform).toBe('translate3d(0px, 0px, 0)'); + + moveMouse(item1, {pageX: 60, pageY: 10}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(50px, 0px, 0)'); + + moveMouse(item1, {pageX: 40, pageY: 40}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(50px, 50px, 0)'); + + moveMouse(item1, {pageX: 440, pageY: 550}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(450px, 550px, 0)'); releaseMouse(item1); }); - it('SnapMirror.grid', async () => { + it('SnapMirror.inRectRange()', async () => { draggable = new Draggable(containers, { draggable: 'li', plugins: [SnapMirror], SnapMirror: { - targets: [SnapMirror.grid({x: 50, y: 50})], + targets: [{x: 100, y: 100, range: SnapMirror.inRectRange([10, 20, 30, 40])}], }, }); - clickMouse(item1, {clientX: 10, clientY: 10}); + clickMouse(item1, {pageX: 10, pageY: 10}); waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); - const mirror = document.querySelector('.draggable-mirror'); - moveMouse(document.body, {clientX: 20, clientY: 10}); + moveMouse(item1, {pageX: 70, pageY: 80}); await waitForPromisesToResolve(); waitForRequestAnimationFrame(); - expect(mirror.style.transform).toBe('translate3d(0px, 0px, 0)'); + expect(mirror.style.transform).toBe('translate3d(70px, 80px, 0)'); - moveMouse(document.body, {clientX: 60, clientY: 10}); + moveMouse(item1, {pageX: 70, pageY: 100}); await waitForPromisesToResolve(); waitForRequestAnimationFrame(); - expect(mirror.style.transform).toBe('translate3d(50px, 0px, 0)'); + expect(mirror.style.transform).toBe('translate3d(100px, 100px, 0)'); - moveMouse(document.body, {clientX: 40, clientY: 40}); + moveMouse(item1, {pageX: 130, pageY: 120}); await waitForPromisesToResolve(); waitForRequestAnimationFrame(); - expect(mirror.style.transform).toBe('translate3d(50px, 50px, 0)'); + expect(mirror.style.transform).toBe('translate3d(130px, 120px, 0)'); - moveMouse(document.body, {clientX: 440, clientY: 550}); + moveMouse(item1, {pageX: 110, pageY: 120}); await waitForPromisesToResolve(); waitForRequestAnimationFrame(); - expect(mirror.style.transform).toBe('translate3d(450px, 550px, 0)'); + expect(mirror.style.transform).toBe('translate3d(100px, 100px, 0)'); releaseMouse(item1); }); From dc03fbb986c9d1a9dc7cce58bb9829398a354e02 Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Thu, 9 Jul 2020 21:25:40 +0800 Subject: [PATCH 09/13] update plugin --- src/Plugins/SnapMirror/SnapMirror.js | 49 ++++++++++++++++++---------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js index 755922fe..da9f58bb 100644 --- a/src/Plugins/SnapMirror/SnapMirror.js +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -7,6 +7,8 @@ const onMirrorDestroy = Symbol('onMirrorDestroy'); const onMirrorMove = Symbol('onMirrorMove'); const onDragOverContainer = Symbol('onDragOverContainer'); const onDragOutContainer = Symbol('onDragOutContainer'); +const getNearestSnapCoordinate = Symbol('getNearest'); +const calcRelativePoints = Symbol('getRelativePoints'); /** * SnapMirror default options @@ -54,6 +56,8 @@ export default class SnapMirror extends AbstractPlugin { this[onMirrorMove] = this[onMirrorMove].bind(this); this[onDragOverContainer] = this[onDragOverContainer].bind(this); this[onDragOutContainer] = this[onDragOutContainer].bind(this); + this[calcRelativePoints] = this[calcRelativePoints].bind(this); + this[getNearestSnapCoordinate] = this[getNearestSnapCoordinate].bind(this); } /** @@ -74,9 +78,9 @@ export default class SnapMirror extends AbstractPlugin { detach() { this.draggable .off('mirror:created', this[onMirrorCreated]) + .off('mirror:move', this[onMirrorMove]) .off('drag:over:container', this[onDragOverContainer]) .off('drag:out:container', this[onDragOutContainer]) - .off('mirror:move', this[onMirrorMove]) .off('mirror:destroy', this[onMirrorDestroy]); } @@ -94,11 +98,9 @@ export default class SnapMirror extends AbstractPlugin { * @private */ [onMirrorCreated](evt) { - const rect = evt.source.getBoundingClientRect(); - // can't get dimensions of mirror in mirror created - // so use source dimensions - this.relativePoints = this.getRelativePoints(rect, evt.originalEvent); + // so use source's dimensions + const rect = evt.source.getBoundingClientRect(); this.offset = { x: rect.x - evt.sensorEvent.clientX, @@ -128,6 +130,7 @@ export default class SnapMirror extends AbstractPlugin { if (!this.overContainer) { return; } + evt.cancel(); requestAnimationFrame(() => { if (!this.overContainer) { @@ -146,20 +149,35 @@ export default class SnapMirror extends AbstractPlugin { }); } + /** + * Drag over handler + * @param {DragOverEvent | DragOverContainer} dragEvent + * @private + */ [onDragOverContainer](evt) { this.overContainer = evt.overContainer; this.overContainer.append(this.mirror); this.mirror.style.position = 'absolute'; + this[calcRelativePoints](); } + /** + * Drag over handler + * @param {DragOverEvent | DragOverContainer} dragEvent + * @private + */ [onDragOutContainer](evt) { - this.overContainer.position = null; this.overContainer = null; evt.sourceContainer.append(this.mirror); this.mirror.style.position = 'fixed'; } - getNearest(coord) { + /** + * Get nearest snap coordinate according to current coordinate, target and relative points. + * @param {Point} coord + * @private + */ + [getNearestSnapCoordinate](coord) { let result = {x: coord.x, y: coord.y}; let distance = Infinity; @@ -195,20 +213,17 @@ export default class SnapMirror extends AbstractPlugin { return result; } - getRelativePoints(rect) { + /** + * Calculate relative points + * @private + */ + [calcRelativePoints]() { + const rect = this.mirror.getBoundingClientRect(); const relativePoints = []; this.options.relativePoints.forEach((point) => { relativePoints.push({x: rect.width * point.x, y: rect.height * point.y}); }); - return relativePoints; - } - - getOffset(container) { - const rect = container.getBoundingClientRect(); - return { - x: rect.x, - y: rect.y, - }; + this.relativePoints = relativePoints; } } From f125f25c121ad75480a7f838544af8bb5f7ac39d Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Tue, 14 Jul 2020 20:57:37 +0800 Subject: [PATCH 10/13] update tests and styles, add types --- .../Plugins/SnapMirror/SnapMirror.html | 14 ++--- .../Plugins/SnapMirror/SnapMirror.scss | 56 ++++++++----------- .../src/content/Plugins/SnapMirror/index.js | 22 ++++++++ src/Plugins/SnapMirror/README.md | 19 +++---- src/Plugins/SnapMirror/SnapMirror.js | 31 +++++++--- src/Plugins/SnapMirror/grid.js | 23 -------- src/Plugins/SnapMirror/index.d.ts | 35 ++++++++++++ src/Plugins/SnapMirror/targets.js | 37 ++++++++++++ .../SnapMirror/tests/SnapMirror.test.js | 1 + 9 files changed, 152 insertions(+), 86 deletions(-) delete mode 100644 src/Plugins/SnapMirror/grid.js create mode 100644 src/Plugins/SnapMirror/index.d.ts create mode 100644 src/Plugins/SnapMirror/targets.js diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.html b/examples/src/content/Plugins/SnapMirror/SnapMirror.html index a1ded72a..51b73a60 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.html +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.html @@ -11,14 +11,6 @@
-
- {{ Block.render('1', {index: 1, draggable: true}) }} - {{ Block.render('2', {index: 2, draggable: true}) }} - {{ Block.render('3', {index: 3, draggable: true}) }} - {{ Block.render('4', {index: 4, draggable: true}) }} - {{ Block.render('5', {index: 5, draggable: true}) }} - {{ Block.render('6', {index: 6, draggable: true}) }} -
{{ Block.render('', {type: 'Hollow'}) }} {{ Block.render('', {type: 'Hollow'}) }} @@ -27,6 +19,12 @@ {{ Block.render('', {type: 'Hollow'}) }} {{ Block.render('', {type: 'Hollow'}) }}
+ {{ Block.render('1', {index: 1, draggable: true}) }} + {{ Block.render('2', {index: 2, draggable: true}) }} + {{ Block.render('3', {index: 3, draggable: true}) }} + {{ Block.render('4', {index: 4, draggable: true}) }} + {{ Block.render('5', {index: 5, draggable: true}) }} + {{ Block.render('6', {index: 6, draggable: true}) }}
{% endmacro %} diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss index c1f6b00b..eee91075 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss @@ -16,6 +16,7 @@ .Block { width: 150px; height: 150px; + position: absolute; } &.draggable-container--over { .Workspace__grid { @@ -45,23 +46,22 @@ height: 64rem; border: 0.6rem solid #212529; overflow: auto; - .Block-wrapper { - display: flex; - .Block { - width: 50px; - height: 50px; - } - .Block:nth-child(1) { - } - .Block:nth-child(2) { - } - .Block:nth-child(3) { - } - .Block:nth-child(4) { - } - .Block:nth-child(5) { - } - .Block:nth-child(6) { + + .Block--isDraggable { + width: 50px; + height: 50px; + position: absolute; + z-index: 2; + } + + $colors: purple palegoldenrod silver pink orange aqua; + $i: 0; + @each $color in $colors { + $i: $i + 1; + .Block--isDraggable:nth-child(#{$i}) { + .BlockContent { + border-color: $color; + } } } @@ -75,23 +75,11 @@ top: 50%; left: 50%; } - .Block--typeHollow:nth-last-child(1) { - transform: rotate(60deg) translateX(200px) rotate(-60deg); - } - .Block--typeHollow:nth-last-child(2) { - transform: rotate(120deg) translateX(200px) rotate(-120deg); - } - .Block--typeHollow:nth-last-child(3) { - transform: rotate(180deg) translateX(200px) rotate(-180deg); - } - .Block--typeHollow:nth-last-child(4) { - transform: rotate(240deg) translateX(200px) rotate(-240deg); - } - .Block--typeHollow:nth-last-child(5) { - transform: rotate(300deg) translateX(200px) rotate(-300deg); - } - .Block--typeHollow:nth-last-child(6) { - transform: rotate(360deg) translateX(200px) rotate(-360deg); + + @for $i from 0 to 6 { + .Block--typeHollow:nth-child(#{$i + 1}) { + transform: rotate($i * 60deg) translateX(200px) rotate($i * -60deg); + } } } } diff --git a/examples/src/content/Plugins/SnapMirror/index.js b/examples/src/content/Plugins/SnapMirror/index.js index 16ca0f31..bd370772 100644 --- a/examples/src/content/Plugins/SnapMirror/index.js +++ b/examples/src/content/Plugins/SnapMirror/index.js @@ -30,6 +30,17 @@ function initMLP() { }, }); + let originalSource; + + draggable.on('mirror:create', (evt) => { + originalSource = evt.originalSource; + }); + + draggable.on('mirror:destroy', (evt) => { + originalSource.style.transform = evt.mirror.style.transform; + console.log(evt); + }); + return draggable; } @@ -52,6 +63,17 @@ function initWorkspace() { }, }); + let originalSource; + + draggable.on('mirror:create', (evt) => { + originalSource = evt.originalSource; + }); + + draggable.on('mirror:destroy', (evt) => { + originalSource.style.transform = evt.mirror.style.transform; + console.log(evt); + }); + return draggable; } diff --git a/src/Plugins/SnapMirror/README.md b/src/Plugins/SnapMirror/README.md index 85996f47..57ef1c3d 100644 --- a/src/Plugins/SnapMirror/README.md +++ b/src/Plugins/SnapMirror/README.md @@ -47,29 +47,24 @@ Target options: | `y` | `number` | The y coordinates of snap target relative to offset. | | `range` | `number` | The range of a snap target is the distance the pointer must be from the target's coordinates for a snap to be possible. | - - **`relativePoints {Array}`** An object with `x` and `y` properties. The `relativePoints` option lets you set where the dragging mirror element should snap. -**`range {Object|Function}`** +**`range {number}`** The `range` option lets you set the default range for all targets. ### Global Method -**`grid(option: Object)`** +**`grid(Object)`** You can use the `SnapMirror.grid()` method to create a target that snaps to a grid. The method takes an object describing a grid and returns a function that snaps to the corners of that grid. -**`inRectRange(range: Array)`** +**`line(Object)`** +You can use the `SnapMirror.line()` method to create a target that snaps to a line. +The method takes an object describing a line and returns a function that snaps to the line. + +**`inRectRange(Array)`** You can use the `SnapMirror.rectRange()` method check if a point in ract Range. ### Examples diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js index da9f58bb..5831d51a 100644 --- a/src/Plugins/SnapMirror/SnapMirror.js +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -1,6 +1,6 @@ import AbstractPlugin from 'shared/AbstractPlugin'; import {distance as euclideanDistance} from 'shared/utils'; -import grid from './grid'; +import {grid, line} from './targets'; const onMirrorCreated = Symbol('onMirrorCreated'); const onMirrorDestroy = Symbol('onMirrorDestroy'); @@ -51,6 +51,11 @@ export default class SnapMirror extends AbstractPlugin { ...this.getOptions(), }; + this.offset = null; + this.mirror = null; + this.overContainer = null; + this.relativePoints = null; + this[onMirrorCreated] = this[onMirrorCreated].bind(this); this[onMirrorDestroy] = this[onMirrorDestroy].bind(this); this[onMirrorMove] = this[onMirrorMove].bind(this); @@ -117,8 +122,9 @@ export default class SnapMirror extends AbstractPlugin { */ [onMirrorDestroy]() { this.offset = null; + this.mirror = null; this.relativePoints = null; - this.startPoint = null; + this.overContainer = null; } /** @@ -127,7 +133,7 @@ export default class SnapMirror extends AbstractPlugin { * @private */ [onMirrorMove](evt) { - if (!this.overContainer) { + if (!this.overContainer || evt.canceled()) { return; } @@ -136,15 +142,14 @@ export default class SnapMirror extends AbstractPlugin { if (!this.overContainer) { return; } - // console.log(evt.originalEvent.pageY, evt.originalEvent.pageX); - // console.log(this.overContainer.scrollTop, this.overContainer.scrollLeft); - // console.log(this.overContainer.offsetTop, this.overContainer.offsetLeft); - const nearest = this.getNearest({ + + const point = { // currentPageX - contianerOffset + contanierScroll + selfOffset x: evt.originalEvent.pageX + this.overContainer.scrollLeft - this.overContainer.offsetLeft + this.offset.x, y: evt.originalEvent.pageY + this.overContainer.scrollTop - this.overContainer.offsetTop + this.offset.y, - }); - // console.log(nearest); + }; + const nearest = this[getNearestSnapCoordinate](point); + this.mirror.style.transform = `translate3d(${nearest.x}px, ${nearest.y}px, 0)`; }); } @@ -155,6 +160,9 @@ export default class SnapMirror extends AbstractPlugin { * @private */ [onDragOverContainer](evt) { + if (evt.canceled()) { + return; + } this.overContainer = evt.overContainer; this.overContainer.append(this.mirror); this.mirror.style.position = 'absolute'; @@ -167,6 +175,9 @@ export default class SnapMirror extends AbstractPlugin { * @private */ [onDragOutContainer](evt) { + if (evt.canceled()) { + return; + } this.overContainer = null; evt.sourceContainer.append(this.mirror); this.mirror.style.position = 'fixed'; @@ -229,6 +240,8 @@ export default class SnapMirror extends AbstractPlugin { SnapMirror.grid = grid; +SnapMirror.line = line; + SnapMirror.inRectRange = function(range) { return function(target, coord) { return ( diff --git a/src/Plugins/SnapMirror/grid.js b/src/Plugins/SnapMirror/grid.js deleted file mode 100644 index f8aea395..00000000 --- a/src/Plugins/SnapMirror/grid.js +++ /dev/null @@ -1,23 +0,0 @@ -export default function grid(gridOptions) { - const { - range, - limits = { - left: -Infinity, - right: Infinity, - top: -Infinity, - bottom: Infinity, - }, - } = gridOptions; - - return function gridFunc(x, y) { - const result = {range, grid, x: null, y: null}; - - const gridx = Math.round(x / gridOptions.x); - const gridy = Math.round(y / gridOptions.y); - - result.x = Math.max(limits.left, Math.min(limits.right, gridx * gridOptions.x)); - result.y = Math.max(limits.top, Math.min(limits.bottom, gridy * gridOptions.y)); - - return result; - }; -} diff --git a/src/Plugins/SnapMirror/index.d.ts b/src/Plugins/SnapMirror/index.d.ts new file mode 100644 index 00000000..c43580d4 --- /dev/null +++ b/src/Plugins/SnapMirror/index.d.ts @@ -0,0 +1,35 @@ +/** + * SnapMirror Plugin + */ + +import type {AbstractPlugin} from "@shopify/draggable"; + +interface Point { + x: number; + y: number; +} + +interface Target { + x: number; + y: number; + range: number; +} + +type TargetFunction = (x: number, y: number, instance) => Target; + +interface SnapMirrorOptions { + targets: Array; + relativePoints: Array; + range: number; +} + +export class SnapMirror extends AbstractPlugin { + options: SnapMirrorOptions; + protected attach(): void; + protected detach(): void; + grid(options: Target): TargetFunction; + line(options: Target): TargetFunction; +} + + + diff --git a/src/Plugins/SnapMirror/targets.js b/src/Plugins/SnapMirror/targets.js new file mode 100644 index 00000000..34cb7ebc --- /dev/null +++ b/src/Plugins/SnapMirror/targets.js @@ -0,0 +1,37 @@ +export function grid(options) { + return function(x, y) { + const result = {range: options.range, x: null, y: null}; + + const gridx = Math.round(x / options.x); + const gridy = Math.round(y / options.y); + + result.x = gridx * options.x; + result.y = gridy * options.y; + + return result; + }; +} + +export function line(options) { + return function(x, y) { + const result = {range: options.range, x: null, y: null}; + + if (!options.y) { + result.y = y; + result.x = options.x; + return result; + } + + if (!options.x) { + result.x = x; + result.y = options.y; + return result; + } + + // TOD0: P1(0, a) P2(b, 0) P3(c, d) -> P(x, y) + result.x = x; + result.y = y; + + return result; + }; +} diff --git a/src/Plugins/SnapMirror/tests/SnapMirror.test.js b/src/Plugins/SnapMirror/tests/SnapMirror.test.js index 227e4943..1f1e4532 100644 --- a/src/Plugins/SnapMirror/tests/SnapMirror.test.js +++ b/src/Plugins/SnapMirror/tests/SnapMirror.test.js @@ -201,6 +201,7 @@ function mockDimensions(element, [top, right, bottom, left]) { node.getBoundingClientRect = function() { return element.getBoundingClientRect(); }; + node.cloneNode = element.cloneNode; return node; }; From e07c6cd6492add210ff200add2f713448ea27342 Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Wed, 15 Jul 2020 19:24:45 +0800 Subject: [PATCH 11/13] add line target --- ...IS-DEFORE-MERGE-snap-mirror-line-test.html | 79 +++++++++++++++++++ src/Plugins/SnapMirror/SnapMirror.js | 2 +- src/Plugins/SnapMirror/targets.js | 24 +++++- .../SnapMirror/tests/SnapMirror.test.js | 79 +++++++++++++++++++ 4 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 DELETE-THIS-DEFORE-MERGE-snap-mirror-line-test.html diff --git a/DELETE-THIS-DEFORE-MERGE-snap-mirror-line-test.html b/DELETE-THIS-DEFORE-MERGE-snap-mirror-line-test.html new file mode 100644 index 00000000..a6df07a6 --- /dev/null +++ b/DELETE-THIS-DEFORE-MERGE-snap-mirror-line-test.html @@ -0,0 +1,79 @@ + + + + + + + Document + + + + +
+ +
+
+ + + + + diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js index 5831d51a..79c261c1 100644 --- a/src/Plugins/SnapMirror/SnapMirror.js +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -150,7 +150,7 @@ export default class SnapMirror extends AbstractPlugin { }; const nearest = this[getNearestSnapCoordinate](point); - this.mirror.style.transform = `translate3d(${nearest.x}px, ${nearest.y}px, 0)`; + this.mirror.style.transform = `translate3d(${Math.round(nearest.x)}px, ${Math.round(nearest.y)}px, 0)`; }); } diff --git a/src/Plugins/SnapMirror/targets.js b/src/Plugins/SnapMirror/targets.js index 34cb7ebc..1ef0c133 100644 --- a/src/Plugins/SnapMirror/targets.js +++ b/src/Plugins/SnapMirror/targets.js @@ -28,10 +28,28 @@ export function line(options) { return result; } - // TOD0: P1(0, a) P2(b, 0) P3(c, d) -> P(x, y) - result.x = x; - result.y = y; + const intersection = verticalIntersection(options, {x, y}); + + result.x = intersection.x; + result.y = intersection.y; return result; }; } + +/** + * Get the coordinates of the foot of perpendicular of the given point on the given line + * @param {*} intercepts x-intercept and y-intercept of the line + * @param {*} point the given point + * + * line: y = b - (b / a) * x + * perpendicular on the point: (y - d) / (x - c) = a / b + */ +function verticalIntersection({x: a, y: b}, {x: c, y: d}) { + const x = (a * a * c + a * b * b - a * d * b) / (a * a + b * b); + const y = b - (b / a) * x; + return { + x, + y, + }; +} diff --git a/src/Plugins/SnapMirror/tests/SnapMirror.test.js b/src/Plugins/SnapMirror/tests/SnapMirror.test.js index 1f1e4532..ac2b8abf 100644 --- a/src/Plugins/SnapMirror/tests/SnapMirror.test.js +++ b/src/Plugins/SnapMirror/tests/SnapMirror.test.js @@ -138,6 +138,85 @@ describe('SnapMirror', () => { releaseMouse(item1); }); + it('SnapMirror.line() with x option', async () => { + draggable = new Draggable(containers, { + draggable: 'li', + plugins: [SnapMirror], + SnapMirror: { + targets: [SnapMirror.line({x: 50})], + }, + }); + + clickMouse(item1, {pageX: 10, pageY: 10}); + waitForDragDelay(); + waitForRequestAnimationFrame(); + await waitForPromisesToResolve(); + const mirror = document.querySelector('.draggable-mirror'); + + moveMouse(item1, {pageX: 20, pageY: 10}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(50px, 10px, 0)'); + + moveMouse(item1, {pageX: 440, pageY: 550}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(50px, 550px, 0)'); + + releaseMouse(item1); + }); + + it('SnapMirror.line() with y option', async () => { + draggable = new Draggable(containers, { + draggable: 'li', + plugins: [SnapMirror], + SnapMirror: { + targets: [SnapMirror.line({y: 50})], + }, + }); + + clickMouse(item1, {pageX: 10, pageY: 10}); + waitForDragDelay(); + waitForRequestAnimationFrame(); + await waitForPromisesToResolve(); + const mirror = document.querySelector('.draggable-mirror'); + + moveMouse(item1, {pageX: 20, pageY: 10}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(20px, 50px, 0)'); + + moveMouse(item1, {pageX: 440, pageY: 550}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(440px, 50px, 0)'); + + releaseMouse(item1); + }); + + it('SnapMirror.line() with x and y options', async () => { + draggable = new Draggable(containers, { + draggable: 'li', + plugins: [SnapMirror], + SnapMirror: { + targets: [SnapMirror.line({x: 30, y: 50})], + }, + }); + + clickMouse(item1, {pageX: 10, pageY: 10}); + waitForDragDelay(); + waitForRequestAnimationFrame(); + await waitForPromisesToResolve(); + const mirror = document.querySelector('.draggable-mirror'); + + moveMouse(item1, {pageX: 20, pageY: 10}); + await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); + expect(mirror.style.transform).toBe('translate3d(23px, 12px, 0)'); + + releaseMouse(item1); + }); + it('SnapMirror.inRectRange()', async () => { draggable = new Draggable(containers, { draggable: 'li', From e9820dfffc643826a518f9656961a7cfa606feeb Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Thu, 16 Jul 2020 19:40:12 +0800 Subject: [PATCH 12/13] reconstruction --- TODO.md | 4 + .../Plugins/SnapMirror/SnapMirror.html | 19 +- .../Plugins/SnapMirror/SnapMirror.scss | 57 +++--- .../src/content/Plugins/SnapMirror/index.js | 34 ++-- src/Plugins/SnapMirror/SnapMirror.js | 168 +++++++++++------- .../SnapMirror/tests/SnapMirror.test.js | 7 + 6 files changed, 162 insertions(+), 127 deletions(-) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..5b7044f3 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +- [ ] Fix bugs on mobile browsers. +- [ ] event offset: Which coordiante given in API? Which coordiante use in code? \ +coordiantes: page, client, container, mirror +- [ ] `SnapMirror.line` edge case diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.html b/examples/src/content/Plugins/SnapMirror/SnapMirror.html index 51b73a60..5ba93aa4 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.html +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.html @@ -10,21 +10,10 @@
-
-
- {{ Block.render('', {type: 'Hollow'}) }} - {{ Block.render('', {type: 'Hollow'}) }} - {{ Block.render('', {type: 'Hollow'}) }} - {{ Block.render('', {type: 'Hollow'}) }} - {{ Block.render('', {type: 'Hollow'}) }} - {{ Block.render('', {type: 'Hollow'}) }} -
- {{ Block.render('1', {index: 1, draggable: true}) }} - {{ Block.render('2', {index: 2, draggable: true}) }} - {{ Block.render('3', {index: 3, draggable: true}) }} - {{ Block.render('4', {index: 4, draggable: true}) }} - {{ Block.render('5', {index: 5, draggable: true}) }} - {{ Block.render('6', {index: 6, draggable: true}) }} +
+ {{ Block.render('drag', {index: 1, draggable: true}) }} + {{ Block.render('drop', {type: 'Hollow'}) }} +
{% endmacro %} diff --git a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss index eee91075..f5698252 100644 --- a/examples/src/content/Plugins/SnapMirror/SnapMirror.scss +++ b/examples/src/content/Plugins/SnapMirror/SnapMirror.scss @@ -8,20 +8,21 @@ .SnapMirror { .Workspace { + $size: 50px; + $border-width: 0.3rem; &.BlockLayout--typePositioned { height: 32rem; border: 0.6rem solid #212529; overflow: auto; .Block { - width: 150px; - height: 150px; + width: calc(#{3 * $size} + #{$border-width}); + height: calc(#{3 * $size} + #{$border-width}); position: absolute; } + &.draggable-container--over { .Workspace__grid { - $size: 50px; - $border-width: 0.3rem; background: linear-gradient(180deg, #00f 0, #00f $border-width, transparent 0, transparent $size) 0px 0px / 100% $size repeat-y, linear-gradient(90deg, #00f 0, #00f $border-width, transparent 0, transparent $size) 0px 0px / #{$size} 100% @@ -31,8 +32,6 @@ } &__grid { - $size: 50px; - $border-width: 0.3rem; width: 1500px; height: 1000px; background: linear-gradient(180deg, #000 0, #000 $border-width, transparent 0, transparent $size) 0px 0px / 100% @@ -41,46 +40,36 @@ } } - .MLP { + .CircleRange { &.BlockLayout--typePositioned { height: 64rem; border: 0.6rem solid #212529; overflow: auto; .Block--isDraggable { - width: 50px; - height: 50px; position: absolute; + width: 20rem; + height: 20rem; z-index: 2; } - $colors: purple palegoldenrod silver pink orange aqua; - $i: 0; - @each $color in $colors { - $i: $i + 1; - .Block--isDraggable:nth-child(#{$i}) { - .BlockContent { - border-color: $color; - } - } + .Block--typeHollow { + position: absolute; + top: calc(50% - 10rem); + left: calc(50% - 10rem); + width: 20rem; + height: 20rem; } - .Hollow-wrapper { - position: relative; - height: 500px; - .Block--typeHollow { - width: 50px; - height: 50px; - position: absolute; - top: 50%; - left: 50%; - } - - @for $i from 0 to 6 { - .Block--typeHollow:nth-child(#{$i + 1}) { - transform: rotate($i * 60deg) translateX(200px) rotate($i * -60deg); - } - } + .circle { + position: absolute; + top: calc(50% - 25rem); + left: calc(50% - 25rem); + width: 50rem; + height: 50rem; + border: 3px solid #000; + border-radius: 50%; + content: ''; } } } diff --git a/examples/src/content/Plugins/SnapMirror/index.js b/examples/src/content/Plugins/SnapMirror/index.js index bd370772..853a858f 100644 --- a/examples/src/content/Plugins/SnapMirror/index.js +++ b/examples/src/content/Plugins/SnapMirror/index.js @@ -1,23 +1,25 @@ // eslint-disable-next-line import/no-unresolved import {Draggable, Plugins} from '@shopify/draggable'; -function initMLP() { - const container = document.querySelector('#SnapMirror .BlockLayout.MLP'); +function initCircle() { + const container = document.querySelector('#SnapMirror .BlockLayout.CircleRange'); const containerRect = container.getBoundingClientRect(); + const circleRect = document.querySelector('.circle').getBoundingClientRect(); const targets = []; [...document.querySelectorAll('.Block--typeHollow')].forEach((star) => { const rect = star.getBoundingClientRect(); - let range = Plugins.SnapMirror.inRectRange([15, 25, 25, 15]); - if (star.classList.contains('star1')) { - range = Plugins.SnapMirror.inRectRange([Infinity, Infinity, Infinity, Infinity]); - } - if (star.classList.contains('star2')) { - range = 20; - } - targets.push({x: rect.x + 20 - containerRect.x, y: rect.y + 20 - containerRect.y, range}); + const range = circleRect.width / 2; + targets.push({ + x: rect.x - containerRect.x + rect.width / 2, + y: rect.y - containerRect.y + rect.width / 2, + range(coord, target, relativePoint, {eventOffset}) { + return (coord.x + eventOffset.x - target.x) ** 2 + (coord.y + eventOffset.y - target.y) ** 2 < range ** 2; + }, + }); }); + console.log(targets); const draggable = new Draggable([container], { draggable: '.Block--isDraggable', mirror: { @@ -37,8 +39,10 @@ function initMLP() { }); draggable.on('mirror:destroy', (evt) => { + if (evt.mirror.style.position !== 'absolute') { + return; + } originalSource.style.transform = evt.mirror.style.transform; - console.log(evt); }); return draggable; @@ -70,8 +74,10 @@ function initWorkspace() { }); draggable.on('mirror:destroy', (evt) => { + if (evt.mirror.style.position !== 'absolute') { + return; + } originalSource.style.transform = evt.mirror.style.transform; - console.log(evt); }); return draggable; @@ -79,7 +85,7 @@ function initWorkspace() { export default function PluginsSnapMirror() { const workspaceDraggable = initWorkspace(); - const MLPDraggable = initMLP(); + const CircleDraggable = initCircle(); - return [workspaceDraggable, MLPDraggable]; + return [workspaceDraggable, CircleDraggable]; } diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js index 79c261c1..29927b67 100644 --- a/src/Plugins/SnapMirror/SnapMirror.js +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -7,7 +7,6 @@ const onMirrorDestroy = Symbol('onMirrorDestroy'); const onMirrorMove = Symbol('onMirrorMove'); const onDragOverContainer = Symbol('onDragOverContainer'); const onDragOutContainer = Symbol('onDragOutContainer'); -const getNearestSnapCoordinate = Symbol('getNearest'); const calcRelativePoints = Symbol('getRelativePoints'); /** @@ -51,10 +50,11 @@ export default class SnapMirror extends AbstractPlugin { ...this.getOptions(), }; - this.offset = null; + this.eventOffset = null; this.mirror = null; this.overContainer = null; this.relativePoints = null; + this.lastAnimationFrame = null; this[onMirrorCreated] = this[onMirrorCreated].bind(this); this[onMirrorDestroy] = this[onMirrorDestroy].bind(this); @@ -62,7 +62,6 @@ export default class SnapMirror extends AbstractPlugin { this[onDragOverContainer] = this[onDragOverContainer].bind(this); this[onDragOutContainer] = this[onDragOutContainer].bind(this); this[calcRelativePoints] = this[calcRelativePoints].bind(this); - this[getNearestSnapCoordinate] = this[getNearestSnapCoordinate].bind(this); } /** @@ -107,9 +106,9 @@ export default class SnapMirror extends AbstractPlugin { // so use source's dimensions const rect = evt.source.getBoundingClientRect(); - this.offset = { - x: rect.x - evt.sensorEvent.clientX, - y: rect.y - evt.sensorEvent.clientY, + this.eventOffset = { + x: evt.sensorEvent.clientX - rect.x, + y: evt.sensorEvent.clientY - rect.y, }; this.mirror = evt.mirror; @@ -121,7 +120,9 @@ export default class SnapMirror extends AbstractPlugin { * @private */ [onMirrorDestroy]() { - this.offset = null; + cancelAnimationFrame(this.lastAnimationFrame); + this.lastAnimationFrame = null; + this.eventOffset = null; this.mirror = null; this.relativePoints = null; this.overContainer = null; @@ -133,24 +134,25 @@ export default class SnapMirror extends AbstractPlugin { * @private */ [onMirrorMove](evt) { - if (!this.overContainer || evt.canceled()) { + if (evt.canceled()) { return; } - evt.cancel(); - requestAnimationFrame(() => { - if (!this.overContainer) { - return; - } + if (this.lastAnimationFrame) { + evt.cancel(); + return; + } - const point = { - // currentPageX - contianerOffset + contanierScroll + selfOffset - x: evt.originalEvent.pageX + this.overContainer.scrollLeft - this.overContainer.offsetLeft + this.offset.x, - y: evt.originalEvent.pageY + this.overContainer.scrollTop - this.overContainer.offsetTop + this.offset.y, - }; - const nearest = this[getNearestSnapCoordinate](point); + if (!this.overContainer) { + return; + } - this.mirror.style.transform = `translate3d(${Math.round(nearest.x)}px, ${Math.round(nearest.y)}px, 0)`; + evt.cancel(); + + cancelAnimationFrame(this.lastAnimationFrame); + this.lastAnimationFrame = requestAnimationFrame(() => { + positionMirror(evt.originalEvent, this); + this.lastAnimationFrame = null; }); } @@ -164,9 +166,15 @@ export default class SnapMirror extends AbstractPlugin { return; } this.overContainer = evt.overContainer; - this.overContainer.append(this.mirror); - this.mirror.style.position = 'absolute'; - this[calcRelativePoints](); + + cancelAnimationFrame(this.lastAnimationFrame); + this.lastAnimationFrame = requestAnimationFrame(() => { + this.overContainer.append(this.mirror); + this[calcRelativePoints](); + this.mirror.style.position = 'absolute'; + positionMirror(evt.originalEvent, this); + this.lastAnimationFrame = null; + }); } /** @@ -179,49 +187,15 @@ export default class SnapMirror extends AbstractPlugin { return; } this.overContainer = null; - evt.sourceContainer.append(this.mirror); - this.mirror.style.position = 'fixed'; - } - - /** - * Get nearest snap coordinate according to current coordinate, target and relative points. - * @param {Point} coord - * @private - */ - [getNearestSnapCoordinate](coord) { - let result = {x: coord.x, y: coord.y}; - let distance = Infinity; - - this.options.targets.forEach((rowTarget) => { - let target = rowTarget; - if (typeof target === 'function') { - target = target(coord.x, coord.y, this); - } - - const range = target.range ? target.range : this.options.range; - this.relativePoints.forEach((relativePoint) => { - const tempPoint = { - x: coord.x + relativePoint.x, - y: coord.y + relativePoint.y, - }; - const tempDistance = euclideanDistance(tempPoint.x, tempPoint.y, target.x, target.y); - - if ((typeof range === 'function' && !range(target, tempPoint)) || tempDistance > range) { - return; - } - - if (tempDistance < distance) { - result = { - x: target.x - relativePoint.x, - y: target.y - relativePoint.y, - }; - distance = tempDistance; - } - }); + cancelAnimationFrame(this.lastAnimationFrame); + this.lastAnimationFrame = requestAnimationFrame(() => { + evt.sourceContainer.append(this.mirror); + this.mirror.style.position = 'fixed'; + positionMirror(evt.originalEvent, this); + cancelAnimationFrame(this.lastAnimationFrame); + this.lastAnimationFrame = null; }); - - return result; } /** @@ -238,6 +212,72 @@ export default class SnapMirror extends AbstractPlugin { } } +function positionMirror(mouseMoveEvent, snapMirror) { + const {mirror, overContainer, eventOffset} = snapMirror; + + if (!overContainer) { + // point relative to client and event offset + const point = { + x: mouseMoveEvent.clientX - eventOffset.x, + y: mouseMoveEvent.clientY - eventOffset.y, + }; + mirror.style.transform = `translate3d(${Math.round(point.x)}px, ${Math.round(point.y)}px, 0)`; + return; + } + + const pointRelativeToPage = { + x: mouseMoveEvent.pageX - eventOffset.x, + y: mouseMoveEvent.pageY - eventOffset.y, + }; + const pointRelativeToContainer = { + x: pointRelativeToPage.x + overContainer.scrollLeft - overContainer.offsetLeft, + y: pointRelativeToPage.y + overContainer.scrollTop - overContainer.offsetTop, + }; + const point = getNearestSnapPoint(pointRelativeToContainer, snapMirror); + mirror.style.transform = `translate3d(${Math.round(point.x)}px, ${Math.round(point.y)}px, 0)`; +} + +/** + * Get nearest snap coordinate according to current coordinate, target and relative points. + * @param {Point} coord + * @private + */ +function getNearestSnapPoint(coord, snapMirror) { + let result = {x: coord.x, y: coord.y}; + let distance = Infinity; + + snapMirror.options.targets.forEach((rowTarget) => { + let target = rowTarget; + if (typeof target === 'function') { + target = target(coord.x, coord.y, snapMirror); + } + + const range = target.range ? target.range : snapMirror.options.range; + + snapMirror.relativePoints.forEach((relativePoint) => { + const tempPoint = { + x: coord.x + relativePoint.x, + y: coord.y + relativePoint.y, + }; + const tempDistance = euclideanDistance(tempPoint.x, tempPoint.y, target.x, target.y); + + if ((typeof range === 'function' && !range(coord, target, relativePoint, snapMirror)) || tempDistance > range) { + return; + } + + if (tempDistance < distance) { + result = { + x: target.x - relativePoint.x, + y: target.y - relativePoint.y, + }; + distance = tempDistance; + } + }); + }); + + return result; +} + SnapMirror.grid = grid; SnapMirror.line = line; diff --git a/src/Plugins/SnapMirror/tests/SnapMirror.test.js b/src/Plugins/SnapMirror/tests/SnapMirror.test.js index ac2b8abf..a92e1e71 100644 --- a/src/Plugins/SnapMirror/tests/SnapMirror.test.js +++ b/src/Plugins/SnapMirror/tests/SnapMirror.test.js @@ -61,6 +61,7 @@ describe('SnapMirror', () => { waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); const mirror = document.querySelector('.draggable-mirror'); moveMouse(item1, {pageX: 50, pageY: 10}); @@ -90,6 +91,7 @@ describe('SnapMirror', () => { waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); const mirror = document.querySelector('.draggable-mirror'); moveMouse(item1, {pageX: 50, pageY: 50}); @@ -113,6 +115,7 @@ describe('SnapMirror', () => { waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); const mirror = document.querySelector('.draggable-mirror'); moveMouse(item1, {pageX: 20, pageY: 10}); @@ -151,6 +154,7 @@ describe('SnapMirror', () => { waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); const mirror = document.querySelector('.draggable-mirror'); moveMouse(item1, {pageX: 20, pageY: 10}); @@ -179,6 +183,7 @@ describe('SnapMirror', () => { waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); const mirror = document.querySelector('.draggable-mirror'); moveMouse(item1, {pageX: 20, pageY: 10}); @@ -207,6 +212,7 @@ describe('SnapMirror', () => { waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); const mirror = document.querySelector('.draggable-mirror'); moveMouse(item1, {pageX: 20, pageY: 10}); @@ -230,6 +236,7 @@ describe('SnapMirror', () => { waitForDragDelay(); waitForRequestAnimationFrame(); await waitForPromisesToResolve(); + waitForRequestAnimationFrame(); const mirror = document.querySelector('.draggable-mirror'); moveMouse(item1, {pageX: 70, pageY: 80}); From eeb951ffc9904c0d0fdc38982b2b6b0cbce6a635 Mon Sep 17 00:00:00 2001 From: JuFeng Zhang Date: Mon, 27 Jul 2020 19:04:24 +0800 Subject: [PATCH 13/13] edit todo, add pageX/Y to SensorEvent --- TODO.md | 4 ++- .../src/content/Plugins/SnapMirror/index.js | 8 +++-- .../Sensors/MouseSensor/MouseSensor.js | 6 ++++ .../Sensors/SensorEvent/SensorEvent.js | 20 ++++++++++++ .../Sensors/TouchSensor/TouchSensor.js | 8 ++++- src/Plugins/SnapMirror/SnapMirror.js | 32 +++++++++++-------- 6 files changed, 60 insertions(+), 18 deletions(-) diff --git a/TODO.md b/TODO.md index 5b7044f3..8a302d66 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,6 @@ - [ ] Fix bugs on mobile browsers. + - [ ] Issue: TouchSensor: Why set pageX/Y to clientX/Y + - [x] Add pageX/Y to SensorEvent - [ ] event offset: Which coordiante given in API? Which coordiante use in code? \ -coordiantes: page, client, container, mirror + coordiantes: page, client, container, mirror - [ ] `SnapMirror.line` edge case diff --git a/examples/src/content/Plugins/SnapMirror/index.js b/examples/src/content/Plugins/SnapMirror/index.js index 853a858f..5707c70a 100644 --- a/examples/src/content/Plugins/SnapMirror/index.js +++ b/examples/src/content/Plugins/SnapMirror/index.js @@ -13,8 +13,12 @@ function initCircle() { targets.push({ x: rect.x - containerRect.x + rect.width / 2, y: rect.y - containerRect.y + rect.width / 2, - range(coord, target, relativePoint, {eventOffset}) { - return (coord.x + eventOffset.x - target.x) ** 2 + (coord.y + eventOffset.y - target.y) ** 2 < range ** 2; + range(coord, target, relativePoint, {pointInMirrorCoordinate}) { + return ( + (coord.x + pointInMirrorCoordinate.x - target.x) ** 2 + + (coord.y + pointInMirrorCoordinate.y - target.y) ** 2 < + range ** 2 + ); }, }); }); diff --git a/src/Draggable/Sensors/MouseSensor/MouseSensor.js b/src/Draggable/Sensors/MouseSensor/MouseSensor.js index 2c28225c..9a737abe 100644 --- a/src/Draggable/Sensors/MouseSensor/MouseSensor.js +++ b/src/Draggable/Sensors/MouseSensor/MouseSensor.js @@ -109,6 +109,8 @@ export default class MouseSensor extends Sensor { const container = this.currentContainer; const dragStartEvent = new DragStartSensorEvent({ + pageX: startEvent.pageX, + pageY: startEvent.pageY, clientX: startEvent.clientX, clientY: startEvent.clientY, target: startEvent.target, @@ -166,6 +168,8 @@ export default class MouseSensor extends Sensor { const target = document.elementFromPoint(event.clientX, event.clientY); const dragMoveEvent = new DragMoveSensorEvent({ + pageX: event.pageX, + pageY: event.pageY, clientX: event.clientX, clientY: event.clientY, target, @@ -199,6 +203,8 @@ export default class MouseSensor extends Sensor { const target = document.elementFromPoint(event.clientX, event.clientY); const dragStopEvent = new DragStopSensorEvent({ + pageX: event.pageX, + pageY: event.pageY, clientX: event.clientX, clientY: event.clientY, target, diff --git a/src/Draggable/Sensors/SensorEvent/SensorEvent.js b/src/Draggable/Sensors/SensorEvent/SensorEvent.js index d5aa4559..0c0fa8e7 100644 --- a/src/Draggable/Sensors/SensorEvent/SensorEvent.js +++ b/src/Draggable/Sensors/SensorEvent/SensorEvent.js @@ -37,6 +37,26 @@ export class SensorEvent extends AbstractEvent { return this.data.clientY; } + /** + * Normalized pageX for both touch and mouse events + * @property pageX + * @type {Number} + * @readonly + */ + get pageX() { + return this.data.pageX; + } + + /** + * Normalized pageY for both touch and mouse events + * @property pageY + * @type {Number} + * @readonly + */ + get pageY() { + return this.data.pageY; + } + /** * Normalized target for both touch and mouse events * Returns the element that is behind cursor or touch pointer diff --git a/src/Draggable/Sensors/TouchSensor/TouchSensor.js b/src/Draggable/Sensors/TouchSensor/TouchSensor.js index 48345d28..1116c8f9 100644 --- a/src/Draggable/Sensors/TouchSensor/TouchSensor.js +++ b/src/Draggable/Sensors/TouchSensor/TouchSensor.js @@ -140,11 +140,13 @@ export default class TouchSensor extends Sensor { [startDrag]() { const startEvent = this.startEvent; const container = this.currentContainer; - const {clientX, clientY} = touchCoords(startEvent); + const {clientX, clientY, pageX, pageY} = touchCoords(startEvent); const dragStartEvent = new DragStartSensorEvent({ clientX, clientY, + pageX, + pageY, target: startEvent.target, container, originalEvent: startEvent, @@ -196,6 +198,8 @@ export default class TouchSensor extends Sensor { const dragMoveEvent = new DragMoveSensorEvent({ clientX, clientY, + pageX, + pageY, target, container: this.currentContainer, originalEvent: event, @@ -235,6 +239,8 @@ export default class TouchSensor extends Sensor { const dragStopEvent = new DragStopSensorEvent({ clientX, clientY, + pageX, + pageY, target, container: this.currentContainer, originalEvent: event, diff --git a/src/Plugins/SnapMirror/SnapMirror.js b/src/Plugins/SnapMirror/SnapMirror.js index 29927b67..b1edb021 100644 --- a/src/Plugins/SnapMirror/SnapMirror.js +++ b/src/Plugins/SnapMirror/SnapMirror.js @@ -50,7 +50,7 @@ export default class SnapMirror extends AbstractPlugin { ...this.getOptions(), }; - this.eventOffset = null; + this.pointInMirrorCoordinate = null; this.mirror = null; this.overContainer = null; this.relativePoints = null; @@ -106,7 +106,7 @@ export default class SnapMirror extends AbstractPlugin { // so use source's dimensions const rect = evt.source.getBoundingClientRect(); - this.eventOffset = { + this.pointInMirrorCoordinate = { x: evt.sensorEvent.clientX - rect.x, y: evt.sensorEvent.clientY - rect.y, }; @@ -122,7 +122,7 @@ export default class SnapMirror extends AbstractPlugin { [onMirrorDestroy]() { cancelAnimationFrame(this.lastAnimationFrame); this.lastAnimationFrame = null; - this.eventOffset = null; + this.pointInMirrorCoordinate = null; this.mirror = null; this.relativePoints = null; this.overContainer = null; @@ -151,7 +151,7 @@ export default class SnapMirror extends AbstractPlugin { cancelAnimationFrame(this.lastAnimationFrame); this.lastAnimationFrame = requestAnimationFrame(() => { - positionMirror(evt.originalEvent, this); + positionMirror(evt.sensorEvent, this); this.lastAnimationFrame = null; }); } @@ -166,13 +166,16 @@ export default class SnapMirror extends AbstractPlugin { return; } this.overContainer = evt.overContainer; + const rect = evt.overContainer.getBoundingClientRect(); + this.overContainer.pageX = window.scrollX + rect.x; + this.overContainer.pageY = window.scrollY + rect.y; cancelAnimationFrame(this.lastAnimationFrame); this.lastAnimationFrame = requestAnimationFrame(() => { this.overContainer.append(this.mirror); this[calcRelativePoints](); this.mirror.style.position = 'absolute'; - positionMirror(evt.originalEvent, this); + positionMirror(evt.sensorEvent, this); this.lastAnimationFrame = null; }); } @@ -192,7 +195,7 @@ export default class SnapMirror extends AbstractPlugin { this.lastAnimationFrame = requestAnimationFrame(() => { evt.sourceContainer.append(this.mirror); this.mirror.style.position = 'fixed'; - positionMirror(evt.originalEvent, this); + positionMirror(evt.sensorEvent, this); cancelAnimationFrame(this.lastAnimationFrame); this.lastAnimationFrame = null; }); @@ -212,27 +215,28 @@ export default class SnapMirror extends AbstractPlugin { } } -function positionMirror(mouseMoveEvent, snapMirror) { - const {mirror, overContainer, eventOffset} = snapMirror; +function positionMirror(sensorEvent, snapMirror) { + const {mirror, overContainer, pointInMirrorCoordinate} = snapMirror; if (!overContainer) { // point relative to client and event offset const point = { - x: mouseMoveEvent.clientX - eventOffset.x, - y: mouseMoveEvent.clientY - eventOffset.y, + x: sensorEvent.clientX - pointInMirrorCoordinate.x, + y: sensorEvent.clientY - pointInMirrorCoordinate.y, }; mirror.style.transform = `translate3d(${Math.round(point.x)}px, ${Math.round(point.y)}px, 0)`; return; } const pointRelativeToPage = { - x: mouseMoveEvent.pageX - eventOffset.x, - y: mouseMoveEvent.pageY - eventOffset.y, + x: sensorEvent.pageX - pointInMirrorCoordinate.x, + y: sensorEvent.pageY - pointInMirrorCoordinate.y, }; const pointRelativeToContainer = { - x: pointRelativeToPage.x + overContainer.scrollLeft - overContainer.offsetLeft, - y: pointRelativeToPage.y + overContainer.scrollTop - overContainer.offsetTop, + x: pointRelativeToPage.x + overContainer.scrollLeft - overContainer.pageX, + y: pointRelativeToPage.y + overContainer.scrollTop - overContainer.pageY, }; + const point = getNearestSnapPoint(pointRelativeToContainer, snapMirror); mirror.style.transform = `translate3d(${Math.round(point.x)}px, ${Math.round(point.y)}px, 0)`; }