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
- Visual Feedback: Show constraint guides clearly to indicate allowed movement
- Consistent Behavior: Use the same constraint patterns throughout your app
- User Control: Allow users to change constraints when appropriate
- Accessibility: Provide clear labels for constraint states
- 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
- Explore Bounded Dragging for area constraints
- Learn about Custom Animations for constraint feedback
- Check out Sortable Lists for ordered arrangements