0
# Virtual Elements
1
2
Support for positioning relative to virtual coordinates for context menus, mouse-following tooltips, and positioning relative to abstract points rather than DOM elements.
3
4
## Capabilities
5
6
### Virtual Element Interface
7
8
Definition for objects that can be used as reference elements without being actual DOM elements.
9
10
```javascript { .api }
11
/**
12
* Virtual element that can be positioned relative to
13
* Must implement getBoundingClientRect to define positioning reference
14
*/
15
interface VirtualElement {
16
/** Returns rectangle defining the virtual element's position and size */
17
getBoundingClientRect(): ClientRect | DOMRect;
18
/** Optional context element for boundary calculations */
19
contextElement?: Element;
20
}
21
22
interface ClientRect {
23
x: number;
24
y: number;
25
top: number;
26
left: number;
27
right: number;
28
bottom: number;
29
width: number;
30
height: number;
31
}
32
```
33
34
**Usage Examples:**
35
36
```javascript
37
import { createPopper } from '@popperjs/core';
38
39
// Mouse cursor virtual element
40
const cursorVirtualElement = {
41
getBoundingClientRect() {
42
return {
43
x: mouseX,
44
y: mouseY,
45
top: mouseY,
46
left: mouseX,
47
right: mouseX,
48
bottom: mouseY,
49
width: 0,
50
height: 0,
51
};
52
},
53
};
54
55
const popper = createPopper(cursorVirtualElement, tooltip, {
56
placement: 'bottom-start',
57
});
58
59
// Update position on mouse move
60
document.addEventListener('mousemove', (event) => {
61
mouseX = event.clientX;
62
mouseY = event.clientY;
63
popper.update();
64
});
65
```
66
67
### Mouse-Following Tooltips
68
69
Create tooltips that follow the mouse cursor.
70
71
```javascript { .api }
72
// No specific API - use VirtualElement with mouse coordinates
73
```
74
75
**Usage Examples:**
76
77
```javascript
78
import { createPopper } from '@popperjs/core';
79
80
let mouseX = 0;
81
let mouseY = 0;
82
83
// Virtual element that tracks mouse position
84
const mouseVirtualElement = {
85
getBoundingClientRect() {
86
return {
87
x: mouseX,
88
y: mouseY,
89
top: mouseY,
90
left: mouseX,
91
right: mouseX,
92
bottom: mouseY,
93
width: 0,
94
height: 0,
95
};
96
},
97
};
98
99
// Create popper with virtual element
100
const mousePopper = createPopper(mouseVirtualElement, tooltip, {
101
placement: 'bottom-start',
102
modifiers: [
103
{
104
name: 'offset',
105
options: {
106
offset: [10, 10], // Offset from cursor
107
},
108
},
109
],
110
});
111
112
// Track mouse movement
113
document.addEventListener('mousemove', (event) => {
114
mouseX = event.clientX;
115
mouseY = event.clientY;
116
117
// Update popper position
118
mousePopper.update();
119
});
120
121
// Show tooltip on hover
122
document.addEventListener('mouseenter', () => {
123
tooltip.style.visibility = 'visible';
124
});
125
126
document.addEventListener('mouseleave', () => {
127
tooltip.style.visibility = 'hidden';
128
});
129
```
130
131
### Context Menus
132
133
Position context menus relative to click coordinates.
134
135
```javascript { .api }
136
// No specific API - use VirtualElement with click coordinates
137
```
138
139
**Usage Examples:**
140
141
```javascript
142
import { createPopper } from '@popperjs/core';
143
144
// Context menu virtual element
145
function createContextMenuVirtualElement(x, y) {
146
return {
147
getBoundingClientRect() {
148
return {
149
x,
150
y,
151
top: y,
152
left: x,
153
right: x,
154
bottom: y,
155
width: 0,
156
height: 0,
157
};
158
},
159
};
160
}
161
162
// Show context menu on right-click
163
document.addEventListener('contextmenu', (event) => {
164
event.preventDefault();
165
166
const virtualElement = createContextMenuVirtualElement(
167
event.clientX,
168
event.clientY
169
);
170
171
const contextPopper = createPopper(virtualElement, contextMenu, {
172
placement: 'bottom-start',
173
modifiers: [
174
{
175
name: 'flip',
176
options: {
177
boundary: 'viewport',
178
},
179
},
180
{
181
name: 'preventOverflow',
182
options: {
183
boundary: 'viewport',
184
},
185
},
186
],
187
});
188
189
contextMenu.style.visibility = 'visible';
190
191
// Hide on click outside
192
const hideMenu = () => {
193
contextMenu.style.visibility = 'hidden';
194
contextPopper.destroy();
195
document.removeEventListener('click', hideMenu);
196
};
197
198
document.addEventListener('click', hideMenu);
199
});
200
```
201
202
### Selection-Based Positioning
203
204
Position tooltips relative to text selections or ranges.
205
206
```javascript { .api }
207
// No specific API - use VirtualElement with selection coordinates
208
```
209
210
**Usage Examples:**
211
212
```javascript
213
import { createPopper } from '@popperjs/core';
214
215
// Create virtual element from text selection
216
function createSelectionVirtualElement() {
217
const selection = window.getSelection();
218
if (!selection.rangeCount) return null;
219
220
const range = selection.getRangeAt(0);
221
const rect = range.getBoundingClientRect();
222
223
return {
224
getBoundingClientRect() {
225
return rect;
226
},
227
contextElement: range.commonAncestorContainer.parentElement,
228
};
229
}
230
231
// Show tooltip for selected text
232
document.addEventListener('selectionchange', () => {
233
const selection = window.getSelection();
234
235
if (selection.toString().length > 0) {
236
const virtualElement = createSelectionVirtualElement();
237
238
if (virtualElement) {
239
const selectionPopper = createPopper(virtualElement, selectionTooltip, {
240
placement: 'top',
241
modifiers: [
242
{
243
name: 'offset',
244
options: {
245
offset: [0, 8],
246
},
247
},
248
],
249
});
250
251
selectionTooltip.style.visibility = 'visible';
252
253
// Store reference for cleanup
254
window.currentSelectionPopper = selectionPopper;
255
}
256
} else {
257
// Hide tooltip when selection is cleared
258
if (window.currentSelectionPopper) {
259
selectionTooltip.style.visibility = 'hidden';
260
window.currentSelectionPopper.destroy();
261
window.currentSelectionPopper = null;
262
}
263
}
264
});
265
```
266
267
### Dynamic Virtual Elements
268
269
Virtual elements that change position or size over time.
270
271
```javascript { .api }
272
// No specific API - implement getBoundingClientRect with dynamic values
273
```
274
275
**Usage Examples:**
276
277
```javascript
278
import { createPopper } from '@popperjs/core';
279
280
// Animated virtual element
281
class AnimatedVirtualElement {
282
constructor(startX, startY, endX, endY, duration) {
283
this.startX = startX;
284
this.startY = startY;
285
this.endX = endX;
286
this.endY = endY;
287
this.duration = duration;
288
this.startTime = Date.now();
289
}
290
291
getBoundingClientRect() {
292
const elapsed = Date.now() - this.startTime;
293
const progress = Math.min(elapsed / this.duration, 1);
294
295
// Easing function
296
const eased = 1 - Math.pow(1 - progress, 3);
297
298
const x = this.startX + (this.endX - this.startX) * eased;
299
const y = this.startY + (this.endY - this.startY) * eased;
300
301
return {
302
x,
303
y,
304
top: y,
305
left: x,
306
right: x,
307
bottom: y,
308
width: 0,
309
height: 0,
310
};
311
}
312
}
313
314
// Create animated tooltip
315
const animatedVirtual = new AnimatedVirtualElement(100, 100, 300, 200, 1000);
316
const animatedPopper = createPopper(animatedVirtual, tooltip, {
317
placement: 'top',
318
});
319
320
// Update during animation
321
const animate = () => {
322
animatedPopper.update();
323
324
const elapsed = Date.now() - animatedVirtual.startTime;
325
if (elapsed < animatedVirtual.duration) {
326
requestAnimationFrame(animate);
327
}
328
};
329
330
animate();
331
```
332
333
### Context Elements
334
335
Using contextElement for proper boundary calculations with virtual elements.
336
337
```javascript { .api }
338
interface VirtualElement {
339
getBoundingClientRect(): ClientRect | DOMRect;
340
/** Element to use for scroll parent and boundary calculations */
341
contextElement?: Element;
342
}
343
```
344
345
**Usage Examples:**
346
347
```javascript
348
import { createPopper } from '@popperjs/core';
349
350
// Virtual element with context for proper scrolling behavior
351
const virtualWithContext = {
352
getBoundingClientRect() {
353
return {
354
x: 150,
355
y: 150,
356
top: 150,
357
left: 150,
358
right: 150,
359
bottom: 150,
360
width: 0,
361
height: 0,
362
};
363
},
364
// Use container for scroll parent detection
365
contextElement: document.querySelector('.scrollable-container'),
366
};
367
368
const popper = createPopper(virtualWithContext, tooltip, {
369
placement: 'bottom',
370
modifiers: [
371
{
372
name: 'preventOverflow',
373
options: {
374
boundary: 'clippingParents', // Will use contextElement's clipping parents
375
},
376
},
377
],
378
});
379
380
// The popper will now properly handle scrolling within .scrollable-container
381
```
382
383
### Error Handling
384
385
Common patterns for handling virtual element edge cases.
386
387
```javascript
388
// Validate virtual element before use
389
function createSafeVirtualElement(x, y, contextElement) {
390
// Ensure coordinates are valid numbers
391
if (typeof x !== 'number' || typeof y !== 'number' ||
392
!isFinite(x) || !isFinite(y)) {
393
throw new Error('Invalid coordinates for virtual element');
394
}
395
396
return {
397
getBoundingClientRect() {
398
return {
399
x,
400
y,
401
top: y,
402
left: x,
403
right: x,
404
bottom: y,
405
width: 0,
406
height: 0,
407
};
408
},
409
contextElement,
410
};
411
}
412
413
// Handle dynamic coordinate updates safely
414
function updateVirtualElementPopper(popper, x, y) {
415
if (popper && typeof x === 'number' && typeof y === 'number') {
416
// Update the virtual element's coordinates
417
// Then update popper position
418
popper.update();
419
}
420
}
421
422
// Clean up event listeners when destroying virtual element poppers
423
function cleanupVirtualPopper(popper) {
424
if (popper) {
425
popper.destroy();
426
// Remove any associated event listeners
427
document.removeEventListener('mousemove', updateHandler);
428
document.removeEventListener('click', clickHandler);
429
}
430
}
431
```