Skip to content

Commit

Permalink
Merge pull request #3 from rick-hup/feat/drag
Browse files Browse the repository at this point in the history
file structure change
  • Loading branch information
rick-hup authored Nov 21, 2024
2 parents bb7e993 + 117e4fa commit c1280c8
Show file tree
Hide file tree
Showing 25 changed files with 369 additions and 67 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ jobs:
- name: Install dependencies
run: pnpm i

- name: PNPM build
run: pnpm run build

- name: Build Docs
run: pnpm docs:build
Expand Down
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
"@vue/compiler-core": "^3.4.14",
"@vue/compiler-dom": "^3.4.14",
"@vue/tsconfig": "^0.5.1",
"motion-v": "workspace:*",
"autoprefixer": "^10.4.16",
"lodash.template": "^4.5.0",
"motion-v": "0.1.0",
"pathe": "^1.1.2",
"rimraf": "^5.0.5",
"shikiji": "^0.10.0-beta.9",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"main": "index.js",
"scripts": {
"dev:play": "pnpm --filter './playground/nuxt' dev",
"dev": "pnpm --filter './packages/vue' dev",
"dev": "pnpm --filter './packages/motion' dev",
"build": "pnpm --filter './packages/**' build",
"test": "pnpm --filter './packages/motion' test",
"prepare": "pnpm simple-git-hooks",
Expand Down
6 changes: 3 additions & 3 deletions packages/motion/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "motion-v",
"version": "0.1.0",
"version": "0.1.1",
"description": "",
"author": "",
"license": "MIT",
Expand All @@ -10,10 +10,10 @@
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.umd.cjs"
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.umd.cjs",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/src/index.d.ts",
"files": [
Expand Down
4 changes: 2 additions & 2 deletions packages/motion/src/components/Motion.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import type { CSSProperties, IntrinsicElementAttributes } from 'vue'
import { Primitive } from './Primitive'
import { isSvgTag } from './utils'
import { MotionState, mountedStates } from '@/state/motion-state'
import { MotionState } from '@/state/motion-state'
type ElementType = keyof IntrinsicElementAttributes
</script>
<script setup lang="ts" generic="T extends ElementType = 'div'">
import { nextTick, onMounted, onUnmounted, onUpdated, ref } from 'vue'
import { onMounted, onUnmounted, onUpdated, ref } from 'vue'
import type { Options } from '@/state/types'
import { usePrimitiveElement } from './usePrimitiveElement'
import { injectAnimatePresence, injectMotion, provideMotion } from './context'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { MotionState } from '../motion-state'
import type { MotionEventNames } from '@/types'
import { Feature } from './feature'
import type { MotionEventNames } from '../event'
import { motionEvent } from '../event'

export class EventFeature extends Feature {
private handlers: Partial<Record<MotionEventNames, (event: Event) => void>> = {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { MotionState } from '@/state/motion-state'
import { EventFeature, HoverGesture, InViewGesture, PressGesture } from './'
import {
// DragGesture,
EventFeature,
HoverGesture,
InViewGesture,
PressGesture,
} from './'

export abstract class Feature {
state: MotionState
Expand All @@ -22,6 +28,7 @@ export class FeatureManager {
new HoverGesture(state),
new PressGesture(state),
new InViewGesture(state),
// new DragGesture(state),
new EventFeature(state),
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { dispatchPointerEvent } from '@/utils/events'
import { Feature } from '@/state/features/feature'
import { Feature } from '@/features/feature'
import type { MotionEventNames } from '@/types'

export abstract class BaseGesture extends Feature {
Expand Down
115 changes: 115 additions & 0 deletions packages/motion/src/features/gestures/drag/index.ts
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()
}
}
150 changes: 150 additions & 0 deletions packages/motion/src/features/gestures/drag/types.ts
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
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { dispatchPointerEvent } from '@/utils/events'
import type { Gesture } from './types'
import type { MotionState } from '@/state/motion-state'
import { BaseGesture } from '@/state/features/gestures'
import { BaseGesture } from '@/features'

function mouseEvent(element: Element, name: 'hoverstart' | 'hoverend', action: VoidFunction) {
function mouseEvent(element: HTMLElement, name: 'hoverstart' | 'hoverend', action: VoidFunction) {
return (event: PointerEvent) => {
if (event.pointerType && event.pointerType !== 'mouse')
return
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dispatchPointerEvent } from '@/utils/events'
import type { MotionState } from '@/state/motion-state'
import { BaseGesture } from '@/state/features/gestures'
import { BaseGesture } from '@/features'
import { inView } from 'framer-motion/dom'

export class InViewGesture extends BaseGesture {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './base'
export * from './hover'
export * from './press'
export * from './in-view'
export * from './drag'
Loading

0 comments on commit c1280c8

Please sign in to comment.