What is createRange?
The Document.createRange() method returns a new Range object whose start and end are both set to offset 0 of the document. A Range represents a fragment of a document that can contain nodes and parts of text nodes, enabling precise document manipulation.
Unlike simple node references, Range objects provide granular control over document regions--whether extracting content, moving elements, or implementing custom text selection behaviors. This capability is fundamental for sophisticated web applications, from rich text editors to content management systems. When working with advanced JavaScript APIs, understanding how to precisely control document regions becomes essential for building complex web experiences.
The Range API has been a stable browser standard since 2015, with consistent support across Chrome, Firefox, Safari, and Edge. This means you can rely on it for production applications without cross-browser compatibility concerns. Whether you're building a collaborative editing tool, implementing drag-and-drop reordering, or creating a custom selection-based feature, the Range interface provides the foundation you need.
Key Points:
- Creates a Range object positioned at document start (offset 0)
- Requires boundary configuration before most operations
- Works consistently across all modern browsers
- Essential for rich text editors and selection-based features
For developers building dynamic web applications, understanding the Range API opens possibilities for creating sophisticated user experiences that go beyond basic DOM manipulation.
1const range = document.createRange();2 3// Initially, the range is at document start (0, 0)4console.log(range.startContainer); // #document5console.log(range.startOffset); // 06console.log(range.collapsed); // true7 8// Set range boundaries9const paragraph = document.querySelector('p');10range.selectNodeContents(paragraph);11 12console.log(range.toString()); // Text content of paragraphSetting Range Boundaries
After creating a range, you must configure its boundaries before performing most operations. The Range API provides multiple methods for precise positioning, each serving different use cases.
Precise Positioning
The setStart() and setEnd() methods allow exact positioning within any node:
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
The offset parameter represents the position within the node--character position for text nodes, and child index for element nodes. This flexibility enables targeting specific text ranges within paragraphs, selecting individual table cells, or precisely positioning insertions within complex document structures.
Relative Positioning
For scenarios where you want to position a range relative to existing nodes, convenience methods simplify common operations:
range.setStartBefore(node); // Range starts immediately before node
range.setStartAfter(node); // Range starts immediately after node
range.setEndBefore(node); // Range ends immediately before node
range.setEndAfter(node); // Range ends immediately after node
These methods are particularly useful when building selection interfaces or implementing drag-and-drop document manipulation, where you need to position ranges relative to user-selected elements.
Selecting Entire Nodes
When you need to select an entire node and its contents, these methods simplify the process:
range.selectNode(node); // Includes the node itself
range.selectNodeContents(node); // Includes only node's children
The distinction is important: selectNode() includes the target node itself in the range, while selectNodeContents() creates a range that encompasses only the children of the specified node. This difference becomes critical when manipulating elements that need to preserve their wrapper or when you only want to work with nested content.
Collapsing Ranges
Collapsing a range sets both start and end to the same position, creating a cursor-like selection point:
range.collapse(); // Collapses to the end point
range.collapse(true); // Collapses to the start point
const isCollapsed = range.collapsed;
This operation is essential for text insertion, cursor positioning, and building selection-based user interfaces.
| Property | Type | Description |
|---|---|---|
| startContainer | Node | The Node containing the range's start |
| startOffset | number | Offset within the start container |
| endContainer | Node | The Node containing the range's end |
| endOffset | number | Offset within the end container |
| collapsed | boolean | True if start and end are at same position |
| commonAncestorContainer | Node | Deepest Node containing both boundaries |
Range Operations: Manipulating Content
Beyond selecting regions, the Range API provides powerful methods for manipulating the content within a range, enabling efficient document transformations without manual DOM traversal.
Extracting Content
The extractContents() method moves the contents of a range from the document into a DocumentFragment and returns that fragment:
const fragment = range.extractContents();
document.body.appendChild(fragment);
This operation preserves the structure of the extracted content, making it ideal for moving elements between locations while maintaining their event listeners and data attributes. The original elements are removed from their source location during extraction.
Cloning Content
When you need to copy content rather than move it, cloneContents() creates a DocumentFragment containing a copy of the range's contents:
const fragment = range.cloneContents();
document.body.appendChild(fragment);
The key difference from extractContents() is that the original content remains in place, allowing you to duplicate regions without affecting the source. This is valuable for features like document templating or content duplication.
Deleting Content
For simply removing content, deleteContents() efficiently removes the range from the document without returning anything:
range.deleteContents();
This method is more efficient than extracting or cloning when you don't need the removed content, as it avoids creating intermediate DocumentFragment objects.
Inserting Content
The insertNode() method inserts a new node at the start of the range:
const newNode = document.createElement('span');
newNode.textContent = 'Inserted';
range.insertNode(newNode);
The inserted node becomes part of the document, positioned at the range's start point. This is useful for adding formatting elements, annotations, or interactive components to specific document positions.
Contextual Fragment
The createContextualFragment() method parses HTML or XML strings within the context of a range:
range.selectNode(document.querySelector('p'));
const fragment = range.createContextualFragment('<strong>Replaced content</strong>');
This method is invaluable for implementing template systems, rich text editors, or any application requiring programmatic HTML insertion with proper parsing and context awareness. Combined with WebRTC and other real-time APIs, these techniques enable sophisticated collaborative editing experiences.
Comparing Ranges
When working with multiple ranges, compareBoundaryPoints() determines the relative position of two ranges:
const comparison = range1.compareBoundaryPoints(Range.END_TO_END, range2);
// Returns: -1 if range1 is before range2
// 0 if they share a boundary
// 1 if range1 is after range2
This capability supports complex selection algorithms, spell-check implementations, and any application requiring range relationship determination.
1// Example: Move first paragraph before second2const paras = document.querySelectorAll('p');3if (paras.length >= 2) {4 const range = document.createRange();5 range.selectNodeContents(paras[0]);6 7 const fragment = range.extractContents();8 range.selectNodeContents(paras[1]);9 paras[1].parentNode.insertBefore(fragment, paras[1]);10}11 12// Example: Wrap text selection in strong tag13const selection = window.getSelection();14if (selection.rangeCount > 0) {15 const range = selection.getRangeAt(0);16 const strong = document.createElement('strong');17 range.surroundContents(strong); // Requires collapsed: false18}19 20// Example: Clone a range for later use21const originalRange = document.createRange();22originalRange.selectNodeContents(document.querySelector('.content'));23const clonedRange = originalRange.cloneRange(); // Independent copyPerformance Best Practices
Working efficiently with ranges requires understanding their performance characteristics and following established best practices that minimize browser reflows and optimize memory usage.
Batch Operations
When performing multiple operations on document content, grouping them within range operations minimizes reflows and repaints. Rather than manipulating individual nodes separately, use range methods to operate on document fragments:
// Efficient: Single range operation
const range = document.createRange();
range.selectNodeContents(container);
const content = range.extractContents();
newContainer.appendChild(content);
// Less efficient: Multiple individual operations
This approach reduces the number of DOM mutations the browser must process, resulting in smoother performance, especially with complex documents or frequent operations.
Minimize Layout Thrashing
Ranges operate on the DOM structure, but reading layout information from ranges requires care. The getBoundingClientRect() and getClientRects() methods return geometry information that can cause layout thrashing if interleaved with style changes:
// Cache measurements before making changes
const rects = Array.from(range.getClientRects());
// Perform range operations
range.deleteContents();
// Use cached rects for measurements
By caching geometry information before making DOM changes, you avoid forcing the browser to recalculate layout multiple times.
Use Ranges for Bulk Operations
When moving or copying multiple elements, ranges are more efficient than individual append and remove operations because they preserve structure and reduce DOM manipulation overhead. This is particularly important for content-rich applications that handle large documents. Pairing efficient range operations with File System API enables powerful document management capabilities in modern browsers.
Proper Cleanup
While modern browsers handle range cleanup automatically, explicitly setting ranges to null when done can aid garbage collection in long-running applications:
const range = document.createRange();
// ... use range ...
range.detach(); // Explicitly detach (optional in modern browsers)
Clone Before Modification
When you need to preserve a selection while performing operations, clone the range first:
const originalRange = selection.getRangeAt(0);
const workingRange = originalRange.cloneRange();
// Modify workingRange without affecting user's selection
This pattern is essential for implementing features like find-and-replace or batch formatting while maintaining the user's cursor position.
Applications where the Range API excels
Rich Text Editors
Implement text editors with precise cursor positioning, text selection, and content formatting using ranges to track selections and manage cursor position.
Drag-and-Drop Reordering
Determine drop targets and identify insertion points with precision exceeding individual node references for smooth document reordering.
Content Extraction
Extract, transform, and republish content while preserving document structure during extraction and enabling clean insertion at new locations.
Selection-Based Features
Build context menus, annotation systems, and collaborative editing tools that respond to user selections accurately.
Common Pitfalls and How to Avoid Them
Understanding common mistakes when working with ranges helps you write more robust code and avoid debugging frustration.
Uninitialized Ranges
A common mistake is attempting operations on a range before setting its boundaries. Always configure start and end points before calling manipulation methods:
// WRONG - range has no boundaries set
const range = document.createRange();
range.deleteContents(); // Error or unexpected behavior
// CORRECT - set boundaries first
const range = document.createRange();
range.selectNode(document.querySelector('div'));
range.deleteContents();
Offset Interpretation
Remember that offsets are interpreted differently based on node type--character position for text nodes, child index for element nodes. A common bug is passing a character offset to an element node:
// For text nodes, offset is character position
const textNode = paragraph.firstChild;
range.setStart(textNode, 5); // Start at 5th character
// For element nodes, offset is child index
const container = document.querySelector('div');
range.setStart(container, 0); // Start at first child
Cross-Frame Limitations
Ranges cannot span multiple documents or frames. When working with iframes, create ranges within each document separately:
// Access iframe's document
const iframe = document.querySelector('iframe');
const iframeDoc = iframe.contentDocument;
// Create range within iframe document
const iframeRange = iframeDoc.createRange();
iframeRange.selectNodeContents(iframeDoc.querySelector('p'));
Live Node References
Nodes extracted or cloned via range operations maintain references to their original documents. When inserting into different documents, use importNode() appropriately:
// Extract from one document
const range = sourceDoc.createRange();
range.selectNodeContents(sourceDoc.querySelector('.content'));
const fragment = range.extractContents();
// Insert into another document
const importedFragment = targetDoc.importNode(fragment, true);
targetDoc.body.appendChild(importedFragment);
SurroundContents Requirements
The surroundContents() method requires that the range doesn't partially select a text node--it must either contain whole nodes or be collapsed:
// WRONG - partially selected text node causes error
range.setStart(textNode, 2);
range.setEnd(textNode, 8);
range.surroundContents(strong); // Throws error
// CORRECT - use extract and wrap pattern
const content = range.extractContents();
const wrapper = document.createElement('strong');
wrapper.appendChild(content);
range.insertNode(wrapper);
Integration with Selection API
The Range API works closely with the Selection API, which represents the user's current text selection in the browser. This integration is essential for implementing features that respond to user selections.
Getting Ranges from Selections
The Selection API provides access to Range objects representing user-selected content:
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
// Manipulate the selected range
// Modify selection to new range
selection.removeAllRanges();
selection.addRange(newRange);
}
The rangeCount property indicates how many ranges are selected--typically 1, but some browsers allow multiple selections.
Modifying Selections
After manipulating a range, you can update the user's selection to reflect changes:
const selection = window.getSelection();
const range = selection.getRangeAt(0);
// Expand selection to include adjacent element
range.selectNode(adjacentElement);
selection.removeAllRanges();
selection.addRange(range);
Practical Applications
This integration enables powerful features:
- Custom context menus: Show options based on selected content
- Browser extensions: Manipulate page content based on user selection
- Collaborative editing: Sync selection state across users
- Text highlighting tools: Mark and annotate selected passages
- Find and replace: Identify and modify text matches while preserving selection
When building interactive web applications, combining the Range and Selection APIs creates sophisticated user experiences that respond naturally to user interactions with text content. For advanced implementations, consider how AI-powered automation can enhance these features with smart suggestions and automated formatting.
Frequently Asked Questions
What is the difference between createRange() and new Range()?
Both create equivalent Range objects. The constructor approach (new Range()) follows modern JavaScript patterns, while createRange() is the traditional DOM method. Use either based on your codebase conventions.
Can ranges work with elements in different documents?
No. A Range is bound to a single document and cannot span multiple documents. When working with iframes, create ranges within each document separately using iframe.contentDocument.createRange().
How do I get the selected text as a Range?
Use the Selection API: const selection = window.getSelection(); if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); }
What does 'collapsed' mean in a Range?
A collapsed Range has equal start and end positions, representing a single point rather than a span. This indicates cursor position or zero-width selection.
How do I check if a point is within a Range?
Use range.isPointInRange(node, offset) which returns true if the point is contained within the Range boundaries.
What is the difference between extractContents() and cloneContents()?
extractContents() moves content from the document to a DocumentFragment (removing it from the original location), while cloneContents() creates a copy leaving the original content in place.
Conclusion
The Document.createRange() method opens access to a powerful API for precise document manipulation. From simple content extraction to complex rich text editing, the Range interface provides the granular control that modern web applications require. Understanding its methods, properties, and best practices enables developers to build more efficient and capable document manipulation systems.
Key takeaways:
- Create ranges with
document.createRange()ornew Range() - Set boundaries using
setStart(),setEnd(), and convenience methods - Manipulate content with
extractContents(),cloneContents(),deleteContents(), andinsertNode() - Follow performance best practices: batch operations, minimize layout thrashing
- Integrate with Selection API for user selection features
- Handle cross-document scenarios with
importNode()
With universal browser support and a stable API dating back to 2015, ranges are a reliable foundation for production applications. By following the best practices outlined in this guide--batch operations, proper cleanup, and understanding node types--you can leverage the full power of the Range API while maintaining optimal performance.
For developers building sophisticated custom web applications, mastering the Range API is essential for creating features like rich text editors, content management tools, and collaborative editing experiences that users expect from modern web software. These capabilities complement other advanced JavaScript features like writable streams for building comprehensive document handling systems.
Related Topics
- Selection API - Working with user text selections
- DOM Manipulation Best Practices - Efficient document updates
- Building Rich Text Editors - Advanced text editing features