-
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from rick-hup/feat/drag
file structure change
- Loading branch information
Showing
25 changed files
with
369 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 1 addition & 3 deletions
4
packages/motion/src/state/features/events.ts → packages/motion/src/features/events.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...otion/src/state/features/gestures/base.ts → ...ages/motion/src/features/gestures/base.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { dispatchPointerEvent } from '@/utils/events' | ||
import type { MotionState } from '@/state/motion-state' | ||
import { BaseGesture } from '@/features/gestures' | ||
import { animate } from 'framer-motion/dom' | ||
import type { DragOptions } from '@/state/types' | ||
|
||
export class DragGesture extends BaseGesture { | ||
private isDragging = false | ||
private currentPosition = { x: 0, y: 0 } | ||
private startPosition = { x: 0, y: 0 } | ||
private constraints?: DragOptions['constraints'] | ||
|
||
isActive() { | ||
return Boolean(this.state.getOptions()) | ||
} | ||
|
||
constructor(state: MotionState) { | ||
super(state) | ||
this.subscribeEvents = () => { | ||
const element = this.state.getElement() | ||
const options = this.state.getOptions().dragOptions || {} | ||
this.constraints = options.constraints | ||
|
||
const onPointerMove = (event: PointerEvent) => { | ||
if (!this.isDragging) | ||
return | ||
|
||
const deltaX = event.clientX - this.startPosition.x | ||
const deltaY = event.clientY - this.startPosition.y | ||
|
||
let newX = this.currentPosition.x + deltaX | ||
let newY = this.currentPosition.y + deltaY | ||
|
||
// 应用约束 | ||
if (this.constraints) { | ||
if (this.constraints.left !== undefined) | ||
newX = Math.max(this.constraints.left, newX) | ||
if (this.constraints.right !== undefined) | ||
newX = Math.min(this.constraints.right, newX) | ||
if (this.constraints.top !== undefined) | ||
newY = Math.max(this.constraints.top, newY) | ||
if (this.constraints.bottom !== undefined) | ||
newY = Math.min(this.constraints.bottom, newY) | ||
} | ||
|
||
// 更新元素位置 | ||
element.style.transform = `translate(${newX}px, ${newY}px)` | ||
|
||
dispatchPointerEvent(element, 'drag', { | ||
...event, | ||
point: { x: newX, y: newY }, | ||
}) | ||
} | ||
|
||
const onPointerUp = (event: PointerEvent) => { | ||
if (!this.isDragging) | ||
return | ||
|
||
this.isDragging = false | ||
this.currentPosition.x += event.clientX - this.startPosition.x | ||
this.currentPosition.y += event.clientY - this.startPosition.y | ||
|
||
this.state.setActive('drag', false) | ||
dispatchPointerEvent(element, 'dragend', event) | ||
|
||
window.removeEventListener('pointermove', onPointerMove) | ||
window.removeEventListener('pointerup', onPointerUp) | ||
|
||
// 处理拖拽结束后的动画 | ||
if (options.dragSnapToOrigin) { | ||
animate(element, { | ||
// eslint-disable-next-line ts/ban-ts-comment | ||
// @ts-expect-error | ||
x: 0, | ||
y: 0, | ||
transition: { type: 'spring', stiffness: 400, damping: 40 }, | ||
}) | ||
this.currentPosition = { x: 0, y: 0 } | ||
} | ||
} | ||
|
||
const onPointerDown = (event: PointerEvent) => { | ||
this.isDragging = true | ||
this.startPosition = { x: event.clientX, y: event.clientY } | ||
|
||
this.state.setActive('drag', true) | ||
dispatchPointerEvent(element, 'dragstart', event) | ||
|
||
window.addEventListener('pointermove', onPointerMove) | ||
window.addEventListener('pointerup', onPointerUp) | ||
} | ||
|
||
// 设置初始样式 | ||
element.style.touchAction = 'none' | ||
element.style.userSelect = 'none' | ||
element.style.cursor = 'grab' | ||
|
||
element.addEventListener('pointerdown', onPointerDown as EventListener) | ||
|
||
return () => { | ||
element.removeEventListener('pointerdown', onPointerDown as EventListener) | ||
window.removeEventListener('pointermove', onPointerMove) | ||
window.removeEventListener('pointerup', onPointerUp) | ||
} | ||
} | ||
} | ||
|
||
mount() { | ||
this.updateGestureSubscriptions() | ||
} | ||
|
||
update() { | ||
this.updateGestureSubscriptions() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import type { VariantLabels } from '@/state/types' | ||
|
||
export interface BoundingBox { | ||
top: number | ||
right: number | ||
bottom: number | ||
left: number | ||
} | ||
type DragElastic = boolean | number | Partial<BoundingBox> | ||
|
||
export interface DragOptions { | ||
/** | ||
* Enable dragging for this element. Set to `false` by default. | ||
* Set `true` to drag in both directions. | ||
* Set `"x"` or `"y"` to only drag in a specific direction. | ||
* | ||
* ```template | ||
* <Motion drag="x" /> | ||
* ``` | ||
*/ | ||
drag?: boolean | 'x' | 'y' | ||
/** | ||
* If `true`, the drag direction will be locked to the initial drag direction. | ||
* Set to `false` by default. | ||
* | ||
* ```template | ||
* <Motion dragOptions="{ directionLock: true, drag: 'x' }" /> | ||
* ``` | ||
*/ | ||
directionLock?: boolean | ||
|
||
/** | ||
* Allows drag gesture propagation to child components. Set to `false` by | ||
* default. | ||
* | ||
* ```template | ||
* <Motion dragOptions="{ propagation: true, drag: 'x' }" /> | ||
* ``` | ||
*/ | ||
propagation?: boolean | ||
/** | ||
* Applies constraints on the permitted draggable area. | ||
* | ||
* It can accept an object of optional `top`, `left`, `right`, and `bottom` values, measured in pixels. | ||
* This will define a distance the named edge of the draggable component. | ||
* | ||
* Alternatively, it can accept a `ref` to another component created with React's `useRef` hook. | ||
* This `ref` should be passed both to the draggable component's `dragConstraints` prop, and the `ref` | ||
* of the component you want to use as constraints. | ||
* | ||
* ```template | ||
* // In pixels | ||
* <Motion dragOptions="{ constraints: { left: 0, right: 300 }, drag: 'x' }" /> | ||
* | ||
* // As a ref to another component | ||
* const MyComponent = () => { | ||
* const constraintsRef = ref() | ||
* | ||
* return ( | ||
* <div ref={constraintsRef}> | ||
* <Motion dragOptions="{ constraints: constraintsRef, drag: 'x' }" /> | ||
* </div> | ||
* ) | ||
* } | ||
* ``` | ||
*/ | ||
constraints?: false | Partial<BoundingBox> | HTMLElement | ||
|
||
/** | ||
* The degree of movement allowed outside constraints. 0 = no movement, 1 = | ||
* full movement. | ||
* | ||
* Set to `0.5` by default. Can also be set as `false` to disable movement. | ||
* | ||
* By passing an object of `top`/`right`/`bottom`/`left`, individual values can be set | ||
* per constraint. Any missing values will be set to `0`. | ||
* | ||
* ```template | ||
* <Motion dragOptions="{ dragElastic: 0.2, constraints: { left: 0, right: 300 }, drag: 'x' }" /> | ||
* ``` | ||
*/ | ||
elastic?: DragElastic | ||
/** | ||
* Apply momentum from the pan gesture to the component when dragging | ||
* finishes. Set to `true` by default. | ||
* | ||
* ```template | ||
* <Motion dragOptions="{ dragMomentum: false, constraints: { left: 0, right: 300 }, drag: 'x' }" /> | ||
* ``` | ||
*/ | ||
momentum?: boolean | ||
/** | ||
* Allows you to change dragging inertia parameters. | ||
* When releasing a draggable Frame, an animation with type `inertia` starts. The animation is based on your dragging velocity. This property allows you to customize it. | ||
* | ||
* ```jsx | ||
* <motion.div | ||
* drag | ||
* dragTransition={{ bounceStiffness: 600, bounceDamping: 10 }} | ||
* /> | ||
* ``` | ||
*/ | ||
// dragTransition?: InertiaOptions | ||
/** | ||
* Usually, dragging is initiated by pressing down on a component and moving it. For some | ||
* use-cases, for instance clicking at an arbitrary point on a video scrubber, we | ||
* might want to initiate dragging from a different component than the draggable one. | ||
* | ||
* By creating a `dragControls` using the `useDragControls` hook, we can pass this into | ||
* the draggable component's `dragControls` prop. It exposes a `start` method | ||
* that can start dragging from pointer events on other components. | ||
* | ||
* ```jsx | ||
* const dragControls = useDragControls() | ||
* | ||
* function startDrag(event) { | ||
* dragControls.start(event, { snapToCursor: true }) | ||
* } | ||
* | ||
* return ( | ||
* <> | ||
* <div onPointerDown={startDrag} /> | ||
* <motion.div drag="x" dragControls={dragControls} /> | ||
* </> | ||
* ) | ||
* ``` | ||
*/ | ||
// dragControls?: DragControls | ||
/** | ||
* If true, element will snap back to its origin when dragging ends. | ||
* | ||
* Enabling this is the equivalent of setting all `dragConstraints` axes to `0` | ||
* with `dragElastic={1}`, but when used together `dragConstraints` can define | ||
* a wider draggable area and `dragSnapToOrigin` will ensure the element | ||
* animates back to its origin on release. | ||
*/ | ||
snapToOrigin?: boolean | ||
} | ||
|
||
export interface DragProps { | ||
/** | ||
* Properties or variant label to animate to while the drag gesture is recognised. | ||
* | ||
* ```jsx | ||
* <motion.div whileDrag={{ scale: 1.2 }} /> | ||
* ``` | ||
*/ | ||
drag?: VariantLabels | ||
dragOptions?: DragOptions | ||
} |
4 changes: 2 additions & 2 deletions
4
...tion/src/state/features/gestures/hover.ts → ...ges/motion/src/features/gestures/hover.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...on/src/state/features/gestures/in-view.ts → ...s/motion/src/features/gestures/in-view.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.