Skip to main content

Axis Constraints

Restrict dragging to specific axes for controlled directional movement.

Overview​

Axis constraints limit draggable items to move only along specific directions (horizontal or vertical). This example demonstrates:

  • Horizontal-only dragging
  • Vertical-only dragging
  • Free movement (both axes)
  • Dynamic axis switching

Key Features​

  • Directional Control: Lock movement to X or Y axis
  • Dynamic Switching: Change constraints during interaction
  • Visual Guides: Show allowed movement directions
  • Smooth Interactions: Natural feel within constraints

Basic Implementation​

import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { DropProvider, Draggable } from 'react-native-reanimated-dnd';

export function AxisConstraintsExample() {
const [constraint, setConstraint] = useState<'x' | 'y' | 'both'>('x');

return (
<GestureHandlerRootView style={styles.container}>
<DropProvider>
<View style={styles.content}>
<Text style={styles.title}>Axis Constraints</Text>

{/* Constraint Controls */}
<View style={styles.controlsContainer}>
{['x', 'y', 'both'].map((axis) => (
<TouchableOpacity
key={axis}
style={[
styles.controlButton,
constraint === axis && styles.activeButton
]}
onPress={() => setConstraint(axis as any)}
>
<Text style={[
styles.controlText,
constraint === axis && styles.activeText
]}>
{axis === 'x' ? 'Horizontal' : axis === 'y' ? 'Vertical' : 'Free'}
</Text>
</TouchableOpacity>
))}
</View>

{/* Drag Area */}
<View style={styles.dragArea}>
<Text style={styles.areaLabel}>Drag Area</Text>

{/* Constraint Guides */}
{constraint === 'x' && <View style={styles.horizontalGuide} />}
{constraint === 'y' && <View style={styles.verticalGuide} />}

{/* Constrained Draggable */}
<Draggable
data={{ id: 'constrained-item', constraint }}
dragAxis={constraint}
style={styles.draggable}
>
<View style={styles.draggableContent}>
<Text style={styles.draggableText}>Drag Me</Text>
<Text style={styles.constraintText}>
{constraint === 'x' ? '← →' : constraint === 'y' ? '↑ ↓' : '↗'}
</Text>
</View>
</Draggable>
</View>

{/* Info */}
<View style={styles.infoContainer}>
<Text style={styles.infoText}>
Current constraint: {constraint === 'x' ? 'Horizontal only' :
constraint === 'y' ? 'Vertical only' : 'No constraints'}
</Text>
</View>
</View>
</DropProvider>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
},
content: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#FFFFFF',
textAlign: 'center',
marginBottom: 30,
},
controlsContainer: {
flexDirection: 'row',
justifyContent: 'center',
marginBottom: 30,
gap: 12,
},
controlButton: {
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 20,
backgroundColor: '#1a1a1a',
borderWidth: 1,
borderColor: '#333333',
},
activeButton: {
backgroundColor: '#58a6ff',
borderColor: '#58a6ff',
},
controlText: {
color: '#8E8E93',
fontSize: 14,
fontWeight: '500',
},
activeText: {
color: '#FFFFFF',
},
dragArea: {
height: 300,
backgroundColor: '#1a1a1a',
borderRadius: 16,
borderWidth: 2,
borderColor: '#333333',
marginBottom: 20,
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
},
areaLabel: {
position: 'absolute',
top: -12,
left: 20,
backgroundColor: '#000000',
color: '#8E8E93',
fontSize: 12,
fontWeight: '600',
paddingHorizontal: 8,
},
horizontalGuide: {
position: 'absolute',
top: '50%',
left: 20,
right: 20,
height: 2,
backgroundColor: '#58a6ff',
opacity: 0.5,
},
verticalGuide: {
position: 'absolute',
left: '50%',
top: 20,
bottom: 20,
width: 2,
backgroundColor: '#58a6ff',
opacity: 0.5,
},
draggable: {
position: 'absolute',
},
draggableContent: {
width: 80,
height: 80,
backgroundColor: '#a2d2ff',
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 6,
elevation: 8,
},
draggableText: {
color: '#000000',
fontWeight: 'bold',
fontSize: 14,
},
constraintText: {
color: '#333333',
fontSize: 16,
marginTop: 4,
},
infoContainer: {
backgroundColor: '#1a1a1a',
borderRadius: 12,
padding: 16,
borderLeftWidth: 4,
borderLeftColor: '#58a6ff',
},
infoText: {
color: '#8E8E93',
fontSize: 14,
lineHeight: 20,
},
});

Constraint Types​

Horizontal Constraint​

<Draggable
data={itemData}
dragAxis="x"
>
{/* Can only move left and right */}
</Draggable>

Vertical Constraint​

<Draggable
data={itemData}
dragAxis="y"
>
{/* Can only move up and down */}
</Draggable>

Free Movement​

<Draggable
data={itemData}
dragAxis="both"
>
{/* Can move in any direction (default) */}
</Draggable>

Advanced Examples​

Dynamic Constraint Switching​

function DynamicConstraintDraggable() {
const [constraint, setConstraint] = useState<'x' | 'y' | 'both'>('x');

return (
<View style={styles.container}>
<View style={styles.controls}>
{['x', 'y', 'both'].map((axis) => (
<TouchableOpacity
key={axis}
onPress={() => setConstraint(axis as any)}
style={[
styles.controlButton,
constraint === axis && styles.activeButton
]}
>
<Text>{axis}</Text>
</TouchableOpacity>
))}
</View>

<Draggable
data={itemData}
dragAxis={constraint}
>
<View style={styles.draggableItem}>
<Text>Constraint: {constraint}</Text>
</View>
</Draggable>
</View>
);
}

Conditional Constraints​

function ConditionalConstraintDraggable({ isLocked }) {
return (
<Draggable
data={itemData}
dragAxis={isLocked ? 'x' : 'both'}
>
<View style={styles.draggableItem}>
<Text>{isLocked ? 'Horizontal Only' : 'Free Movement'}</Text>
</View>
</Draggable>
);
}

Slider Components​

function HorizontalSlider({ value, onValueChange }) {
return (
<View style={styles.sliderContainer}>
<Draggable
data={{ value }}
dragAxis="x"
onDragging={({ x, tx }) => {
// Calculate new value based on position
const newValue = calculateValueFromPosition(x + tx);
onValueChange(newValue);
}}
>
<View style={styles.sliderThumb}>
<Text>{value}</Text>
</View>
</Draggable>
</View>
);
}

function VerticalSlider({ value, onValueChange }) {
return (
<View style={styles.sliderContainer}>
<Draggable
data={{ value }}
dragAxis="y"
onDragging={({ y, ty }) => {
// Calculate new value based on position
const newValue = calculateValueFromPosition(y + ty);
onValueChange(newValue);
}}
>
<View style={styles.sliderThumb}>
<Text>{value}</Text>
</View>
</Draggable>
</View>
);
}

Common Use Cases​

  • Sliders: Horizontal or vertical value controls
  • Resizing: Constrain resize handles to specific directions
  • Scrolling: Lock scroll direction in custom scroll views
  • Games: Restrict character movement to specific paths
  • Form Controls: Directional input components

Best Practices​

  1. Visual Feedback: Show constraint guides clearly to indicate allowed movement
  2. Consistent Behavior: Use the same constraint patterns throughout your app
  3. User Control: Allow users to change constraints when appropriate
  4. Accessibility: Provide clear labels for constraint states
  5. Performance: Axis constraints can improve performance by reducing calculations

Limitations​

  • Library Constraints: Only supports 'x', 'y', and 'both' axis constraints
  • No Custom Angles: The library doesn't support diagonal or custom angle constraints
  • No Snap-to-Axis: Automatic snapping functionality is not built-in

Next Steps​