0
# DOM Query APIs
1
2
Web-compatible DOM querying and observation APIs for component interaction and layout management, providing tools for selecting elements, observing intersections, and monitoring media queries.
3
4
## Capabilities
5
6
### Selector Query
7
8
Query and retrieve information about DOM elements in the current page.
9
10
```typescript { .api }
11
/**
12
* Create a selector query instance for DOM operations
13
* @returns SelectorQuery instance for chaining operations
14
*/
15
function createSelectorQuery(): SelectorQuery;
16
17
interface SelectorQuery {
18
/** Select single element by CSS selector */
19
select(selector: string): NodesRef;
20
/** Select all elements matching CSS selector */
21
selectAll(selector: string): NodesRef;
22
/** Select the viewport */
23
selectViewport(): NodesRef;
24
/** Execute the query and get results */
25
exec(callback?: (res: any[]) => void): void;
26
}
27
28
interface NodesRef {
29
/** Get bounding client rect information */
30
boundingClientRect(callback?: (rect: BoundingClientRect) => void): NodesRef;
31
/** Get scroll offset information */
32
scrollOffset(callback?: (scroll: ScrollOffset) => void): NodesRef;
33
/** Get element context (limited in H5) */
34
context(callback?: (context: any) => void): NodesRef;
35
/** Get element properties */
36
fields(fields: FieldsOptions, callback?: (fields: any) => void): NodesRef;
37
/** Get element node information */
38
node(callback?: (node: NodeInfo) => void): NodesRef;
39
}
40
41
interface BoundingClientRect {
42
id: string;
43
dataset: Record<string, any>;
44
left: number;
45
right: number;
46
top: number;
47
bottom: number;
48
width: number;
49
height: number;
50
}
51
52
interface ScrollOffset {
53
id: string;
54
dataset: Record<string, any>;
55
scrollLeft: number;
56
scrollTop: number;
57
scrollWidth: number;
58
scrollHeight: number;
59
}
60
61
interface FieldsOptions {
62
id?: boolean;
63
dataset?: boolean;
64
rect?: boolean;
65
size?: boolean;
66
scrollOffset?: boolean;
67
properties?: string[];
68
computedStyle?: string[];
69
context?: boolean;
70
mark?: boolean;
71
node?: boolean;
72
}
73
74
interface NodeInfo {
75
id: string;
76
dataset: Record<string, any>;
77
node: HTMLElement;
78
}
79
```
80
81
**Usage Examples:**
82
83
```typescript
84
import { createSelectorQuery } from "@tarojs/taro-h5";
85
86
// Basic element selection and measurement
87
function measureElement() {
88
const query = createSelectorQuery();
89
90
query.select('#my-element')
91
.boundingClientRect((rect) => {
92
console.log('Element position:', {
93
left: rect.left,
94
top: rect.top,
95
width: rect.width,
96
height: rect.height
97
});
98
});
99
100
query.exec();
101
}
102
103
// Multiple element queries
104
function queryMultipleElements() {
105
const query = createSelectorQuery();
106
107
query.selectAll('.list-item')
108
.boundingClientRect()
109
.fields({
110
id: true,
111
dataset: true,
112
properties: ['innerHTML', 'className']
113
});
114
115
query.select('#scroll-container')
116
.scrollOffset();
117
118
query.exec((results) => {
119
const [listItems, scrollContainer] = results;
120
121
console.log('List items:', listItems);
122
console.log('Scroll position:', scrollContainer);
123
});
124
}
125
126
// Viewport measurements
127
function getViewportInfo() {
128
const query = createSelectorQuery();
129
130
query.selectViewport()
131
.boundingClientRect()
132
.scrollOffset();
133
134
query.exec((results) => {
135
const [viewport] = results;
136
console.log('Viewport:', {
137
width: viewport.width,
138
height: viewport.height,
139
scrollTop: viewport.scrollTop,
140
scrollLeft: viewport.scrollLeft
141
});
142
});
143
}
144
145
// Advanced element information
146
function getDetailedElementInfo(selector: string) {
147
return new Promise((resolve) => {
148
const query = createSelectorQuery();
149
150
query.select(selector)
151
.fields({
152
id: true,
153
dataset: true,
154
rect: true,
155
size: true,
156
scrollOffset: true,
157
properties: ['tagName', 'innerHTML', 'className'],
158
computedStyle: ['display', 'position', 'zIndex', 'opacity'],
159
node: true
160
}, (result) => {
161
resolve(result);
162
});
163
164
query.exec();
165
});
166
}
167
168
// Usage
169
const elementInfo = await getDetailedElementInfo('#complex-element');
170
console.log('Detailed element info:', elementInfo);
171
```
172
173
### Intersection Observer
174
175
Observe when elements enter or leave the viewport or intersect with other elements.
176
177
```typescript { .api }
178
/**
179
* Create intersection observer for monitoring element visibility
180
* @param component - Component context (optional in H5)
181
* @param options - Observer configuration options
182
* @returns IntersectionObserver instance
183
*/
184
function createIntersectionObserver(
185
component?: any,
186
options?: IntersectionObserverInit
187
): IntersectionObserver;
188
189
interface IntersectionObserver {
190
/** Start observing target elements */
191
observe(targetSelector: string, callback: IntersectionCallback): void;
192
/** Stop observing specific target */
193
unobserve(targetSelector?: string): void;
194
/** Stop observing all targets */
195
disconnect(): void;
196
/** Set relative positioning element */
197
relativeTo(selector: string, margins?: IntersectionMargins): IntersectionObserver;
198
/** Set relative positioning to viewport */
199
relativeToViewport(margins?: IntersectionMargins): IntersectionObserver;
200
}
201
202
interface IntersectionObserverInit {
203
/** Root element for intersection (default: viewport) */
204
root?: string;
205
/** Margin around root element */
206
rootMargin?: string;
207
/** Threshold values for triggering callback */
208
thresholds?: number[];
209
/** Initial ratio (not standard) */
210
initialRatio?: number;
211
/** Observe all (not standard) */
212
observeAll?: boolean;
213
}
214
215
interface IntersectionMargins {
216
/** Top margin in pixels */
217
top?: number;
218
/** Right margin in pixels */
219
right?: number;
220
/** Bottom margin in pixels */
221
bottom?: number;
222
/** Left margin in pixels */
223
left?: number;
224
}
225
226
interface IntersectionCallbackResult {
227
/** Intersection ratio (0-1) */
228
intersectionRatio: number;
229
/** Intersection rectangle */
230
intersectionRect: DOMRect;
231
/** Bounding rectangle of target element */
232
boundingClientRect: DOMRect;
233
/** Bounding rectangle of root element */
234
rootBounds: DOMRect;
235
/** Target element identifier */
236
id: string;
237
/** Target element dataset */
238
dataset: Record<string, any>;
239
/** Time when intersection occurred */
240
time: number;
241
}
242
243
type IntersectionCallback = (result: IntersectionCallbackResult) => void;
244
```
245
246
**Usage Examples:**
247
248
```typescript
249
import { createIntersectionObserver } from "@tarojs/taro-h5";
250
251
// Basic intersection observation
252
function observeElementVisibility() {
253
const observer = createIntersectionObserver(null, {
254
thresholds: [0, 0.25, 0.5, 0.75, 1.0],
255
rootMargin: '0px 0px -100px 0px' // 100px before bottom of viewport
256
});
257
258
observer.observe('.lazy-image', (result) => {
259
console.log(`Element visibility: ${Math.round(result.intersectionRatio * 100)}%`);
260
261
if (result.intersectionRatio > 0.1) {
262
// Element is at least 10% visible
263
loadImage(result.id);
264
}
265
});
266
}
267
268
// Lazy loading implementation
269
class LazyLoader {
270
private observer: IntersectionObserver;
271
private loadedImages = new Set<string>();
272
273
constructor() {
274
this.observer = createIntersectionObserver(null, {
275
rootMargin: '50px', // Start loading 50px before element enters viewport
276
thresholds: [0]
277
});
278
}
279
280
observeImages() {
281
this.observer.observe('[data-lazy-src]', (result) => {
282
if (result.intersectionRatio > 0 && !this.loadedImages.has(result.id)) {
283
this.loadImage(result);
284
}
285
});
286
}
287
288
private loadImage(result: IntersectionCallbackResult) {
289
const imageId = result.id;
290
const lazySrc = result.dataset.lazySrc;
291
292
if (lazySrc && !this.loadedImages.has(imageId)) {
293
// Load the image
294
const img = new Image();
295
img.onload = () => {
296
// Update the actual image source
297
const element = document.getElementById(imageId);
298
if (element && element.tagName === 'IMG') {
299
(element as HTMLImageElement).src = lazySrc;
300
element.classList.add('loaded');
301
}
302
303
this.loadedImages.add(imageId);
304
console.log('Lazy loaded image:', imageId);
305
};
306
img.src = lazySrc;
307
}
308
}
309
310
disconnect() {
311
this.observer.disconnect();
312
}
313
}
314
315
// Infinite scroll implementation
316
class InfiniteScroll {
317
private observer: IntersectionObserver;
318
private isLoading = false;
319
private onLoadMore: () => Promise<void>;
320
321
constructor(onLoadMore: () => Promise<void>) {
322
this.onLoadMore = onLoadMore;
323
this.observer = createIntersectionObserver(null, {
324
rootMargin: '100px', // Trigger 100px before reaching the sentinel
325
thresholds: [0]
326
});
327
}
328
329
observe(sentinelSelector: string) {
330
this.observer.observe(sentinelSelector, async (result) => {
331
if (result.intersectionRatio > 0 && !this.isLoading) {
332
this.isLoading = true;
333
334
try {
335
await this.onLoadMore();
336
} catch (error) {
337
console.error('Failed to load more content:', error);
338
} finally {
339
this.isLoading = false;
340
}
341
}
342
});
343
}
344
345
disconnect() {
346
this.observer.disconnect();
347
}
348
}
349
350
// Usage
351
const lazyLoader = new LazyLoader();
352
lazyLoader.observeImages();
353
354
const infiniteScroll = new InfiniteScroll(async () => {
355
console.log('Loading more content...');
356
// Load more content logic here
357
});
358
infiniteScroll.observe('#load-more-sentinel');
359
360
// Element animation on scroll
361
function observeForAnimations() {
362
const observer = createIntersectionObserver(null, {
363
thresholds: [0.1, 0.5, 0.9]
364
});
365
366
observer.observe('.animate-on-scroll', (result) => {
367
const element = document.getElementById(result.id);
368
if (!element) return;
369
370
if (result.intersectionRatio > 0.1) {
371
element.classList.add('fade-in');
372
}
373
374
if (result.intersectionRatio > 0.5) {
375
element.classList.add('slide-up');
376
}
377
378
if (result.intersectionRatio > 0.9) {
379
element.classList.add('fully-visible');
380
}
381
});
382
}
383
```
384
385
### Media Query Observer
386
387
Monitor CSS media query changes for responsive design handling.
388
389
```typescript { .api }
390
/**
391
* Create media query observer for responsive design
392
* @returns MediaQueryObserver instance
393
*/
394
function createMediaQueryObserver(): MediaQueryObserver;
395
396
interface MediaQueryObserver {
397
/** Start observing media query changes */
398
observe(descriptor: MediaQueryDescriptor, callback: MediaQueryCallback): void;
399
/** Stop observing media query changes */
400
unobserve(): void;
401
/** Disconnect the observer */
402
disconnect(): void;
403
}
404
405
interface MediaQueryDescriptor {
406
/** CSS media query string */
407
minWidth?: number;
408
maxWidth?: number;
409
orientation?: 'portrait' | 'landscape';
410
}
411
412
interface MediaQueryResult {
413
/** Whether the media query matches */
414
matches: boolean;
415
}
416
417
type MediaQueryCallback = (result: MediaQueryResult) => void;
418
```
419
420
**Usage Examples:**
421
422
```typescript
423
import { createMediaQueryObserver } from "@tarojs/taro-h5";
424
425
// Responsive design handling
426
class ResponsiveManager {
427
private observer: MediaQueryObserver;
428
private breakpoints = {
429
mobile: 768,
430
tablet: 1024,
431
desktop: 1200
432
};
433
434
constructor() {
435
this.observer = createMediaQueryObserver();
436
this.setupBreakpointObservers();
437
}
438
439
private setupBreakpointObservers() {
440
// Mobile breakpoint
441
this.observer.observe(
442
{ maxWidth: this.breakpoints.mobile - 1 },
443
(result) => {
444
if (result.matches) {
445
this.handleMobileView();
446
}
447
}
448
);
449
450
// Tablet breakpoint
451
this.observer.observe(
452
{
453
minWidth: this.breakpoints.mobile,
454
maxWidth: this.breakpoints.tablet - 1
455
},
456
(result) => {
457
if (result.matches) {
458
this.handleTabletView();
459
}
460
}
461
);
462
463
// Desktop breakpoint
464
this.observer.observe(
465
{ minWidth: this.breakpoints.desktop },
466
(result) => {
467
if (result.matches) {
468
this.handleDesktopView();
469
}
470
}
471
);
472
473
// Orientation changes
474
this.observer.observe(
475
{ orientation: 'portrait' },
476
(result) => {
477
this.handleOrientationChange(result.matches ? 'portrait' : 'landscape');
478
}
479
);
480
}
481
482
private handleMobileView() {
483
console.log('Switched to mobile view');
484
document.body.classList.add('mobile');
485
document.body.classList.remove('tablet', 'desktop');
486
}
487
488
private handleTabletView() {
489
console.log('Switched to tablet view');
490
document.body.classList.add('tablet');
491
document.body.classList.remove('mobile', 'desktop');
492
}
493
494
private handleDesktopView() {
495
console.log('Switched to desktop view');
496
document.body.classList.add('desktop');
497
document.body.classList.remove('mobile', 'tablet');
498
}
499
500
private handleOrientationChange(orientation: 'portrait' | 'landscape') {
501
console.log('Orientation changed to:', orientation);
502
document.body.classList.toggle('portrait', orientation === 'portrait');
503
document.body.classList.toggle('landscape', orientation === 'landscape');
504
}
505
506
disconnect() {
507
this.observer.disconnect();
508
}
509
}
510
511
// Usage
512
const responsiveManager = new ResponsiveManager();
513
514
// Simple media query observation
515
function observeScreenSize() {
516
const observer = createMediaQueryObserver();
517
518
observer.observe({ maxWidth: 600 }, (result) => {
519
if (result.matches) {
520
console.log('Small screen detected');
521
// Adjust UI for small screens
522
} else {
523
console.log('Large screen detected');
524
// Adjust UI for large screens
525
}
526
});
527
}
528
```
529
530
## Advanced Usage Patterns
531
532
Complex DOM query scenarios and performance optimization techniques.
533
534
```typescript
535
// Performance-optimized DOM queries
536
class DOMQueryManager {
537
private queryCache = new Map<string, any>();
538
private cacheTimeout = 1000; // 1 second cache
539
540
async queryWithCache(selector: string, fields: FieldsOptions): Promise<any> {
541
const cacheKey = `${selector}:${JSON.stringify(fields)}`;
542
const cached = this.queryCache.get(cacheKey);
543
544
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
545
return cached.data;
546
}
547
548
const data = await this.performQuery(selector, fields);
549
550
this.queryCache.set(cacheKey, {
551
data,
552
timestamp: Date.now()
553
});
554
555
return data;
556
}
557
558
private performQuery(selector: string, fields: FieldsOptions): Promise<any> {
559
return new Promise((resolve) => {
560
const query = createSelectorQuery();
561
562
query.select(selector)
563
.fields(fields, (result) => {
564
resolve(result);
565
});
566
567
query.exec();
568
});
569
}
570
571
clearCache() {
572
this.queryCache.clear();
573
}
574
}
575
576
// Batch DOM operations
577
class BatchDOMOperations {
578
private operations: (() => void)[] = [];
579
private isProcessing = false;
580
581
addOperation(operation: () => void) {
582
this.operations.push(operation);
583
this.scheduleExecution();
584
}
585
586
private scheduleExecution() {
587
if (this.isProcessing) return;
588
589
this.isProcessing = true;
590
591
requestAnimationFrame(() => {
592
// Execute all queued operations
593
while (this.operations.length > 0) {
594
const operation = this.operations.shift();
595
if (operation) {
596
try {
597
operation();
598
} catch (error) {
599
console.error('Batch DOM operation failed:', error);
600
}
601
}
602
}
603
604
this.isProcessing = false;
605
});
606
}
607
608
measureElements(selectors: string[]): Promise<BoundingClientRect[]> {
609
return new Promise((resolve) => {
610
const query = createSelectorQuery();
611
const results: BoundingClientRect[] = [];
612
613
selectors.forEach((selector, index) => {
614
query.select(selector)
615
.boundingClientRect((rect) => {
616
results[index] = rect;
617
});
618
});
619
620
query.exec(() => {
621
resolve(results);
622
});
623
});
624
}
625
}
626
627
// Usage examples
628
const queryManager = new DOMQueryManager();
629
const batchOps = new BatchDOMOperations();
630
631
// Cached queries
632
const elementInfo = await queryManager.queryWithCache('#my-element', {
633
rect: true,
634
properties: ['innerHTML']
635
});
636
637
// Batch measurements
638
const measurements = await batchOps.measureElements([
639
'#header',
640
'#content',
641
'#footer'
642
]);
643
644
console.log('Element measurements:', measurements);
645
```
646
647
## Types
648
649
```typescript { .api }
650
interface DOMRect {
651
x: number;
652
y: number;
653
width: number;
654
height: number;
655
top: number;
656
right: number;
657
bottom: number;
658
left: number;
659
}
660
661
type MediaQueryString = string;
662
type CSSSelector = string;
663
664
interface QueryResult {
665
id: string;
666
dataset: Record<string, any>;
667
[key: string]: any;
668
}
669
```