React Native Reanimated DnDReact Native Reanimated DnD
Examples

Collision Detection

Configure how draggable items detect collisions with drop zones using different algorithms.

Overview

Collision detection determines when a draggable item is considered to be "over" a drop zone. The library provides three built-in algorithms to suit different use cases:

  • intersect: Any overlap between draggable and droppable (default)
  • center: Center point of draggable must be over droppable
  • contain: Entire draggable must be within droppable bounds

Key Features

  • Multiple Algorithms: Choose the best detection method for your use case
  • Visual Feedback: Clear indication when collision is detected
  • Performance Optimized: Efficient calculations for smooth interactions
  • Customizable: Per-draggable configuration options
  • Real-time Updates: Immediate feedback during drag operations

Basic Implementation

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

interface DraggableItemData {
  id: string;
  label: string;
  color: string;
}

export function CollisionDetectionExample() {
  const [activeZone, setActiveZone] = useState<string | null>(null);

  const items: DraggableItemData[] = [
    { id: "1", label: "Center", color: "#ff6b6b" },
    { id: "2", label: "Intersect", color: "#4ecdc4" },
    { id: "3", label: "Contain", color: "#45b7d1" },
  ];

  return (
    <GestureHandlerRootView style={styles.container}>
      <DropProvider>
        <View style={styles.content}>
          <Text style={styles.title}>Collision Detection</Text>
          <Text style={styles.subtitle}>
            Different algorithms for detecting when items are over drop zones
          </Text>

          {/* Drop Zones */}
          <View style={styles.dropZonesContainer}>
            <Droppable<DraggableItemData>
              droppableId="center-zone"
              onDrop={(data) =>
                console.log(`${data.label} dropped on center zone`)
              }
              onActiveChange={(isActive) =>
                setActiveZone(isActive ? "center-zone" : null)
              }
              style={[
                styles.dropZone,
                styles.centerZone,
                activeZone === "center-zone" && styles.activeZone,
              ]}
            >
              <Text style={styles.zoneTitle}>Center Detection</Text>
              <Text style={styles.zoneDescription}>
                Center point must be over zone
              </Text>
            </Droppable>

            <Droppable<DraggableItemData>
              droppableId="intersect-zone"
              onDrop={(data) =>
                console.log(`${data.label} dropped on intersect zone`)
              }
              onActiveChange={(isActive) =>
                setActiveZone(isActive ? "intersect-zone" : null)
              }
              style={[
                styles.dropZone,
                styles.intersectZone,
                activeZone === "intersect-zone" && styles.activeZone,
              ]}
            >
              <Text style={styles.zoneTitle}>Intersect Detection</Text>
              <Text style={styles.zoneDescription}>
                Any overlap triggers detection
              </Text>
            </Droppable>

            <Droppable<DraggableItemData>
              droppableId="contain-zone"
              onDrop={(data) =>
                console.log(`${data.label} dropped on contain zone`)
              }
              onActiveChange={(isActive) =>
                setActiveZone(isActive ? "contain-zone" : null)
              }
              style={[
                styles.dropZone,
                styles.containZone,
                activeZone === "contain-zone" && styles.activeZone,
              ]}
            >
              <Text style={styles.zoneTitle}>Contain Detection</Text>
              <Text style={styles.zoneDescription}>
                Entire item must be inside zone
              </Text>
            </Droppable>
          </View>

          {/* Draggable Items */}
          <View style={styles.draggableItemsArea}>
            <Draggable<DraggableItemData>
              data={items[0]}
              collisionAlgorithm="center"
              style={[styles.draggable, { backgroundColor: items[0].color }]}
            >
              <View style={styles.itemContent}>
                <Text style={styles.itemLabel}>{items[0].label}</Text>
                <Text style={styles.itemHint}>Center detection</Text>
              </View>
            </Draggable>

            <Draggable<DraggableItemData>
              data={items[1]}
              collisionAlgorithm="intersect"
              style={[styles.draggable, { backgroundColor: items[1].color }]}
            >
              <View style={styles.itemContent}>
                <Text style={styles.itemLabel}>{items[1].label}</Text>
                <Text style={styles.itemHint}>Intersect detection</Text>
              </View>
            </Draggable>

            <Draggable<DraggableItemData>
              data={items[2]}
              collisionAlgorithm="contain"
              style={[styles.draggable, { backgroundColor: items[2].color }]}
            >
              <View style={styles.itemContent}>
                <Text style={styles.itemLabel}>{items[2].label}</Text>
                <Text style={styles.itemHint}>Contain detection</Text>
              </View>
            </Draggable>
          </View>

          {/* Info */}
          <View style={styles.infoContainer}>
            <Text style={styles.infoTitle}>How it works:</Text>
            <View style={styles.infoItem}>
              <View
                style={[styles.infoIndicator, { backgroundColor: "#ff6b6b" }]}
              />
              <Text style={styles.infoText}>
                <Text style={styles.bold}>Center:</Text> Precise targeting,
                center point must be over zone
              </Text>
            </View>
            <View style={styles.infoItem}>
              <View
                style={[styles.infoIndicator, { backgroundColor: "#4ecdc4" }]}
              />
              <Text style={styles.infoText}>
                <Text style={styles.bold}>Intersect:</Text> Easy dropping, any
                overlap triggers detection
              </Text>
            </View>
            <View style={styles.infoItem}>
              <View
                style={[styles.infoIndicator, { backgroundColor: "#45b7d1" }]}
              />
              <Text style={styles.infoText}>
                <Text style={styles.bold}>Contain:</Text> Strict placement,
                entire item must fit inside
              </Text>
            </View>
          </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: 8,
  },
  subtitle: {
    fontSize: 15,
    color: "#8E8E93",
    textAlign: "center",
    marginBottom: 30,
    lineHeight: 22,
  },
  dropZonesContainer: {
    gap: 16,
    marginBottom: 40,
  },
  dropZone: {
    height: 100,
    borderRadius: 12,
    borderWidth: 2,
    borderStyle: "dashed",
    justifyContent: "center",
    alignItems: "center",
    padding: 16,
  },
  centerZone: {
    borderColor: "#ff6b6b",
    backgroundColor: "rgba(255, 107, 107, 0.1)",
  },
  intersectZone: {
    borderColor: "#4ecdc4",
    backgroundColor: "rgba(78, 205, 196, 0.1)",
  },
  containZone: {
    borderColor: "#45b7d1",
    backgroundColor: "rgba(69, 183, 209, 0.1)",
  },
  activeZone: {
    borderColor: "#FFFFFF",
    backgroundColor: "rgba(255, 255, 255, 0.2)",
    transform: [{ scale: 1.02 }],
  },
  zoneTitle: {
    fontSize: 16,
    fontWeight: "bold",
    color: "#FFFFFF",
    marginBottom: 4,
  },
  zoneDescription: {
    fontSize: 12,
    color: "#8E8E93",
    textAlign: "center",
  },
  draggableItemsArea: {
    flexDirection: "row",
    justifyContent: "space-around",
    marginBottom: 40,
    gap: 12,
  },
  draggable: {
    width: 100,
    height: 80,
    borderRadius: 12,
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.3,
    shadowRadius: 6,
    elevation: 8,
  },
  itemContent: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    padding: 8,
  },
  itemLabel: {
    fontSize: 14,
    fontWeight: "bold",
    color: "#FFFFFF",
    marginBottom: 2,
  },
  itemHint: {
    fontSize: 10,
    color: "rgba(255, 255, 255, 0.8)",
    textAlign: "center",
  },
  infoContainer: {
    backgroundColor: "#1a1a1a",
    borderRadius: 12,
    padding: 16,
    borderLeftWidth: 4,
    borderLeftColor: "#58a6ff",
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: "bold",
    color: "#FFFFFF",
    marginBottom: 12,
  },
  infoItem: {
    flexDirection: "row",
    alignItems: "center",
    gap: 12,
    marginBottom: 8,
  },
  infoIndicator: {
    width: 12,
    height: 12,
    borderRadius: 6,
  },
  infoText: {
    fontSize: 14,
    color: "#8E8E93",
    flex: 1,
    lineHeight: 20,
  },
  bold: {
    fontWeight: "bold",
    color: "#FFFFFF",
  },
});

Collision Algorithms

Center Detection

The center point of the draggable must be over the droppable:

<Draggable data={itemData} collisionAlgorithm="center">
  {/* Content */}
</Draggable>

Use cases:

  • Precise targeting required
  • Small drop zones
  • Grid-based layouts
  • Slot-based interfaces

Intersect Detection (Default)

Any overlap between draggable and droppable triggers detection:

<Draggable data={itemData} collisionAlgorithm="intersect">
  {/* Content */}
</Draggable>

Use cases:

  • General drag and drop
  • Large drop zones
  • User-friendly interactions
  • File management

Contain Detection

The entire draggable must be within the droppable bounds:

<Draggable data={itemData} collisionAlgorithm="contain">
  {/* Content */}
</Draggable>

Use cases:

  • Strict placement requirements
  • Container-based layouts
  • Puzzle games
  • Form validation

Advanced Examples

Mixed Algorithm Interface

function MixedAlgorithmExample() {
  return (
    <DropProvider>
      <View style={styles.container}>
        {/* Precise targeting zone */}
        <Droppable droppableId="precise-zone">
          <Text>Precise Zone (use center algorithm)</Text>
        </Droppable>

        {/* Easy drop zone */}
        <Droppable droppableId="easy-zone">
          <Text>Easy Zone (use intersect algorithm)</Text>
        </Droppable>

        {/* Strict placement zone */}
        <Droppable droppableId="strict-zone">
          <Text>Strict Zone (use contain algorithm)</Text>
        </Droppable>

        {/* Items with different algorithms */}
        <Draggable data={{ id: "1" }} collisionAlgorithm="center">
          <Text>Precise Item</Text>
        </Draggable>

        <Draggable data={{ id: "2" }} collisionAlgorithm="intersect">
          <Text>Easy Item</Text>
        </Draggable>

        <Draggable data={{ id: "3" }} collisionAlgorithm="contain">
          <Text>Strict Item</Text>
        </Draggable>
      </View>
    </DropProvider>
  );
}

Dynamic Algorithm Switching

function DynamicAlgorithmExample() {
  const [algorithm, setAlgorithm] = useState<
    "center" | "intersect" | "contain"
  >("intersect");
  const [precision, setPrecision] = useState("normal");

  const getAlgorithm = () => {
    switch (precision) {
      case "high":
        return "center";
      case "low":
        return "intersect";
      case "strict":
        return "contain";
      default:
        return "intersect";
    }
  };

  return (
    <DropProvider>
      <View style={styles.container}>
        <View style={styles.controls}>
          <Text>Precision:</Text>
          {["high", "normal", "low", "strict"].map((level) => (
            <TouchableOpacity
              key={level}
              onPress={() => setPrecision(level)}
              style={[
                styles.button,
                precision === level && styles.activeButton,
              ]}
            >
              <Text>{level}</Text>
            </TouchableOpacity>
          ))}
        </View>

        <Draggable data={{ id: "dynamic" }} collisionAlgorithm={getAlgorithm()}>
          <Text>Dynamic Algorithm Item</Text>
        </Draggable>
      </View>
    </DropProvider>
  );
}

Visual Feedback Patterns

Algorithm-Specific Feedback

function AlgorithmFeedback({ algorithm, isActive }) {
  const getFeedbackStyle = () => {
    const baseStyle = styles.dropZone;

    if (!isActive) return baseStyle;

    switch (algorithm) {
      case "center":
        return [baseStyle, styles.centerActive];
      case "intersect":
        return [baseStyle, styles.intersectActive];
      case "contain":
        return [baseStyle, styles.containActive];
      default:
        return baseStyle;
    }
  };

  return (
    <View style={getFeedbackStyle()}>
      <Text>{algorithm} detection</Text>
      {isActive && <Text>āœ“ Collision detected</Text>}
    </View>
  );
}

Real-time Collision Indicator

function CollisionIndicator({ draggableId, droppableId, algorithm }) {
  const [isColliding, setIsColliding] = useState(false);

  return (
    <View style={styles.indicator}>
      <Text>Algorithm: {algorithm}</Text>
      <View
        style={[
          styles.status,
          isColliding ? styles.colliding : styles.notColliding,
        ]}
      >
        <Text>{isColliding ? "Colliding" : "Not Colliding"}</Text>
      </View>
    </View>
  );
}

Performance Considerations

Optimizing Collision Detection

// Use appropriate algorithm for your use case
const getOptimalAlgorithm = (useCase: string) => {
  switch (useCase) {
    case "file-manager":
      return "intersect"; // Easy dropping
    case "grid-layout":
      return "center"; // Precise placement
    case "container-puzzle":
      return "contain"; // Strict fitting
    default:
      return "intersect";
  }
};

// Minimize collision checks for better performance
<Draggable
  data={itemData}
  collisionAlgorithm={getOptimalAlgorithm("file-manager")}
  // Other optimizations handled by library
>
  {/* Content */}
</Draggable>;

Common Use Cases

File Management

// Easy file dropping with intersect
<Draggable collisionAlgorithm="intersect">
  <FileIcon />
</Draggable>

Grid Layouts

// Precise grid placement with center
<Draggable collisionAlgorithm="center">
  <GridItem />
</Draggable>

Container Interfaces

// Strict container fitting with contain
<Draggable collisionAlgorithm="contain">
  <ContainerItem />
</Draggable>

Best Practices

  1. Choose Appropriate Algorithm: Match algorithm to use case requirements
  2. Provide Visual Feedback: Show users when collision is detected
  3. Consider User Experience: Balance precision with ease of use
  4. Test Different Scenarios: Verify behavior with various item sizes
  5. Performance Optimization: Use the most efficient algorithm for your needs

Troubleshooting

Common Issues

Collision not detected:

  • Check if algorithm matches your expectations
  • Verify drop zone size is appropriate
  • Ensure proper positioning

Too sensitive/not sensitive enough:

  • Switch between algorithms
  • Adjust drop zone sizes
  • Consider user feedback

Performance issues:

  • Use simpler algorithms when possible
  • Optimize component re-renders
  • Check for unnecessary calculations

Next Steps