or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

canvas.mdcore-framework.mddevice.mddom-query.mdindex.mdlocation.mdmedia.mdnavigation.mdnetwork.mdstorage.mdsystem-info.mdui-interactions.md

dom-query.mddocs/

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

```