The CSS Custom Highlight API provides powerful capabilities for creating custom text highlighting effects. Let's explore how to implement this feature effectively.
Understanding Custom Highlight API
The Custom Highlight API offers several advantages:
- Programmatic text highlighting
- Multiple highlight ranges
- Custom styling options
- Performance optimization
- Dynamic updates
Basic Implementation
1. Creating Highlights
Set up basic highlighting:
class HighlightManager {
constructor() {
this.registry = new Highlight();
CSS.highlights.set('custom-highlight', this.registry);
}
highlight(element, startOffset, endOffset) {
const range = new StaticRange({
startContainer: element,
startOffset,
endContainer: element,
endOffset
});
this.registry.add(range);
}
clear() {
this.registry.clear();
}
}
// CSS styles
::highlight(custom-highlight) {
background-color: #ffeb3b;
color: #000;
}
2. Range Selection
Implement range selection:
class RangeSelector {
constructor() {
this.highlights = new HighlightManager();
}
selectWord(element) {
const text = element.textContent;
const words = text.split(/\s+/);
let offset = 0;
words.forEach(word => {
const start = text.indexOf(word, offset);
const end = start + word.length;
this.highlights.highlight(element, start, end);
offset = end;
});
}
}
Advanced Features
1. Multiple Highlight Types
Create different highlight styles:
class MultiHighlightManager {
constructor() {
this.highlights = new Map();
}
createHighlight(name, style) {
const registry = new Highlight();
CSS.highlights.set(name, registry);
this.highlights.set(name, {
registry,
style
});
this.addStyle(name, style);
}
addStyle(name, style) {
const stylesheet = document.styleSheets[0];
stylesheet.insertRule(`
::highlight(${name}) {
${Object.entries(style).map(([key, value]) =>
`${key}: ${value};`
).join('\n')}
}
`);
}
highlight(name, element, start, end) {
const highlight = this.highlights.get(name);
if (!highlight) return;
const range = new StaticRange({
startContainer: element,
startOffset: start,
endContainer: element,
endOffset: end
});
highlight.registry.add(range);
}
}
2. Dynamic Updates
Handle dynamic content changes:
class DynamicHighlighter {
constructor() {
this.observer = new MutationObserver(
this.handleMutations.bind(this)
);
this.highlights = new MultiHighlightManager();
}
observe(element) {
this.observer.observe(element, {
childList: true,
characterData: true,
subtree: true
});
}
handleMutations(mutations) {
mutations.forEach(mutation => {
if (mutation.type === 'characterData') {
this.updateHighlights(mutation.target);
}
});
}
updateHighlights(element) {
// Recalculate and update highlights
this.highlights.clear();
this.processElement(element);
}
}
Performance Optimization
1. Batch Processing
Implement efficient batch highlighting:
class BatchHighlighter {
constructor() {
this.queue = [];
this.processing = false;
}
addToQueue(element, ranges) {
this.queue.push({ element, ranges });
if (!this.processing) {
this.processQueue();
}
}
async processQueue() {
this.processing = true;
while (this.queue.length > 0) {
const batch = this.queue.splice(0, 100);
await this.processBatch(batch);
await new Promise(resolve =>
requestAnimationFrame(resolve)
);
}
this.processing = false;
}
async processBatch(batch) {
const registry = new Highlight();
batch.forEach(({ element, ranges }) => {
ranges.forEach(({ start, end }) => {
const range = new StaticRange({
startContainer: element,
startOffset: start,
endContainer: element,
endOffset: end
});
registry.add(range);
});
});
CSS.highlights.set('batch-highlight', registry);
}
}
2. Virtual Scrolling
Optimize for large content:
class VirtualHighlighter {
constructor() {
this.visibleRanges = new Map();
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this)
);
}
observe(element) {
this.observer.observe(element);
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.highlightVisible(entry.target);
} else {
this.removeHighlight(entry.target);
}
});
}
highlightVisible(element) {
const ranges = this.calculateRanges(element);
const registry = new Highlight();
ranges.forEach(range => registry.add(range));
this.visibleRanges.set(element, registry);
CSS.highlights.set(
`highlight-${element.id}`,
registry
);
}
}
Styling Options
1. Advanced Styles
Create sophisticated highlight styles:
class StyleManager {
constructor() {
this.styles = new Map();
}
addStyle(name, properties) {
const style = `
::highlight(${name}) {
background: ${properties.background || 'yellow'};
color: ${properties.color || 'black'};
border-radius: ${properties.borderRadius || '0'};
box-shadow: ${properties.boxShadow || 'none'};
text-decoration: ${properties.textDecoration || 'none'};
font-weight: ${properties.fontWeight || 'normal'};
}
`;
const stylesheet = document.createElement('style');
stylesheet.textContent = style;
document.head.appendChild(stylesheet);
this.styles.set(name, stylesheet);
}
removeStyle(name) {
const stylesheet = this.styles.get(name);
if (stylesheet) {
stylesheet.remove();
this.styles.delete(name);
}
}
}
2. Animation Support
Add highlight animations:
class AnimatedHighlighter {
constructor() {
this.setupStyles();
}
setupStyles() {
const style = `
@keyframes highlight-fade-in {
from {
background-color: transparent;
}
to {
background-color: yellow;
}
}
::highlight(animated) {
animation: highlight-fade-in 0.3s ease-in-out;
background-color: yellow;
}
`;
const stylesheet = document.createElement('style');
stylesheet.textContent = style;
document.head.appendChild(stylesheet);
}
highlight(element, start, end) {
const registry = new Highlight();
const range = new StaticRange({
startContainer: element,
startOffset: start,
endContainer: element,
endOffset: end
});
registry.add(range);
CSS.highlights.set('animated', registry);
}
}
Best Practices
- Performance
- Use batch processing
- Implement virtual scrolling
- Optimize range calculations
- Clean up unused highlights
- Accessibility
- Maintain text contrast
- Support high contrast mode
- Preserve selection behavior
- Consider color blindness
- User Experience
- Provide smooth animations
- Handle user interactions
- Support touch devices
- Maintain responsiveness
Conclusion
The CSS Custom Highlight API provides powerful highlighting capabilities:
- Benefits
- Flexible highlighting
- Performance optimization
- Custom styling options
- Dynamic updates
- Implementation Tips
- Use appropriate techniques
- Optimize performance
- Consider accessibility
- Handle edge cases
- Best Practices
- Batch operations
- Clean up resources
- Handle errors
- Test thoroughly
Remember to:
- Optimize performance
- Consider accessibility
- Handle edge cases
- Test across browsers