CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tarojs--taro-h5

H5端API库,为Taro跨端开发框架提供Web/H5端的API实现

Pending
Overview
Eval results
Files

dom-query.mddocs/

DOM Query APIs

Web-compatible DOM querying and observation APIs for component interaction and layout management, providing tools for selecting elements, observing intersections, and monitoring media queries.

Capabilities

Selector Query

Query and retrieve information about DOM elements in the current page.

/**
 * Create a selector query instance for DOM operations
 * @returns SelectorQuery instance for chaining operations
 */
function createSelectorQuery(): SelectorQuery;

interface SelectorQuery {
  /** Select single element by CSS selector */
  select(selector: string): NodesRef;
  /** Select all elements matching CSS selector */
  selectAll(selector: string): NodesRef;
  /** Select the viewport */
  selectViewport(): NodesRef;
  /** Execute the query and get results */
  exec(callback?: (res: any[]) => void): void;
}

interface NodesRef {
  /** Get bounding client rect information */
  boundingClientRect(callback?: (rect: BoundingClientRect) => void): NodesRef;
  /** Get scroll offset information */
  scrollOffset(callback?: (scroll: ScrollOffset) => void): NodesRef;
  /** Get element context (limited in H5) */
  context(callback?: (context: any) => void): NodesRef;
  /** Get element properties */
  fields(fields: FieldsOptions, callback?: (fields: any) => void): NodesRef;
  /** Get element node information */
  node(callback?: (node: NodeInfo) => void): NodesRef;
}

interface BoundingClientRect {
  id: string;
  dataset: Record<string, any>;
  left: number;
  right: number;
  top: number;
  bottom: number;
  width: number;
  height: number;
}

interface ScrollOffset {
  id: string;
  dataset: Record<string, any>;
  scrollLeft: number;
  scrollTop: number;
  scrollWidth: number;
  scrollHeight: number;
}

interface FieldsOptions {
  id?: boolean;
  dataset?: boolean;
  rect?: boolean;
  size?: boolean;
  scrollOffset?: boolean;
  properties?: string[];
  computedStyle?: string[];
  context?: boolean;
  mark?: boolean;
  node?: boolean;
}

interface NodeInfo {
  id: string;
  dataset: Record<string, any>;
  node: HTMLElement;
}

Usage Examples:

import { createSelectorQuery } from "@tarojs/taro-h5";

// Basic element selection and measurement
function measureElement() {
  const query = createSelectorQuery();
  
  query.select('#my-element')
    .boundingClientRect((rect) => {
      console.log('Element position:', {
        left: rect.left,
        top: rect.top,
        width: rect.width,
        height: rect.height
      });
    });
  
  query.exec();
}

// Multiple element queries
function queryMultipleElements() {
  const query = createSelectorQuery();
  
  query.selectAll('.list-item')
    .boundingClientRect()
    .fields({
      id: true,
      dataset: true,
      properties: ['innerHTML', 'className']
    });
  
  query.select('#scroll-container')
    .scrollOffset();
  
  query.exec((results) => {
    const [listItems, scrollContainer] = results;
    
    console.log('List items:', listItems);
    console.log('Scroll position:', scrollContainer);
  });
}

// Viewport measurements
function getViewportInfo() {
  const query = createSelectorQuery();
  
  query.selectViewport()
    .boundingClientRect()
    .scrollOffset();
  
  query.exec((results) => {
    const [viewport] = results;
    console.log('Viewport:', {
      width: viewport.width,
      height: viewport.height,
      scrollTop: viewport.scrollTop,
      scrollLeft: viewport.scrollLeft
    });
  });
}

// Advanced element information
function getDetailedElementInfo(selector: string) {
  return new Promise((resolve) => {
    const query = createSelectorQuery();
    
    query.select(selector)
      .fields({
        id: true,
        dataset: true,
        rect: true,
        size: true,
        scrollOffset: true,
        properties: ['tagName', 'innerHTML', 'className'],
        computedStyle: ['display', 'position', 'zIndex', 'opacity'],
        node: true
      }, (result) => {
        resolve(result);
      });
    
    query.exec();
  });
}

// Usage
const elementInfo = await getDetailedElementInfo('#complex-element');
console.log('Detailed element info:', elementInfo);

Intersection Observer

Observe when elements enter or leave the viewport or intersect with other elements.

/**
 * Create intersection observer for monitoring element visibility
 * @param component - Component context (optional in H5)
 * @param options - Observer configuration options
 * @returns IntersectionObserver instance
 */
function createIntersectionObserver(
  component?: any,
  options?: IntersectionObserverInit
): IntersectionObserver;

interface IntersectionObserver {
  /** Start observing target elements */
  observe(targetSelector: string, callback: IntersectionCallback): void;
  /** Stop observing specific target */
  unobserve(targetSelector?: string): void;
  /** Stop observing all targets */
  disconnect(): void;
  /** Set relative positioning element */
  relativeTo(selector: string, margins?: IntersectionMargins): IntersectionObserver;
  /** Set relative positioning to viewport */
  relativeToViewport(margins?: IntersectionMargins): IntersectionObserver;
}

interface IntersectionObserverInit {
  /** Root element for intersection (default: viewport) */
  root?: string;
  /** Margin around root element */
  rootMargin?: string;
  /** Threshold values for triggering callback */
  thresholds?: number[];
  /** Initial ratio (not standard) */
  initialRatio?: number;
  /** Observe all (not standard) */
  observeAll?: boolean;
}

interface IntersectionMargins {
  /** Top margin in pixels */
  top?: number;
  /** Right margin in pixels */
  right?: number;
  /** Bottom margin in pixels */
  bottom?: number;
  /** Left margin in pixels */
  left?: number;
}

interface IntersectionCallbackResult {
  /** Intersection ratio (0-1) */
  intersectionRatio: number;
  /** Intersection rectangle */
  intersectionRect: DOMRect;
  /** Bounding rectangle of target element */
  boundingClientRect: DOMRect;
  /** Bounding rectangle of root element */
  rootBounds: DOMRect;
  /** Target element identifier */
  id: string;
  /** Target element dataset */
  dataset: Record<string, any>;
  /** Time when intersection occurred */
  time: number;
}

type IntersectionCallback = (result: IntersectionCallbackResult) => void;

Usage Examples:

import { createIntersectionObserver } from "@tarojs/taro-h5";

// Basic intersection observation
function observeElementVisibility() {
  const observer = createIntersectionObserver(null, {
    thresholds: [0, 0.25, 0.5, 0.75, 1.0],
    rootMargin: '0px 0px -100px 0px' // 100px before bottom of viewport
  });
  
  observer.observe('.lazy-image', (result) => {
    console.log(`Element visibility: ${Math.round(result.intersectionRatio * 100)}%`);
    
    if (result.intersectionRatio > 0.1) {
      // Element is at least 10% visible
      loadImage(result.id);
    }
  });
}

// Lazy loading implementation
class LazyLoader {
  private observer: IntersectionObserver;
  private loadedImages = new Set<string>();
  
  constructor() {
    this.observer = createIntersectionObserver(null, {
      rootMargin: '50px', // Start loading 50px before element enters viewport
      thresholds: [0]
    });
  }
  
  observeImages() {
    this.observer.observe('[data-lazy-src]', (result) => {
      if (result.intersectionRatio > 0 && !this.loadedImages.has(result.id)) {
        this.loadImage(result);
      }
    });
  }
  
  private loadImage(result: IntersectionCallbackResult) {
    const imageId = result.id;
    const lazySrc = result.dataset.lazySrc;
    
    if (lazySrc && !this.loadedImages.has(imageId)) {
      // Load the image
      const img = new Image();
      img.onload = () => {
        // Update the actual image source
        const element = document.getElementById(imageId);
        if (element && element.tagName === 'IMG') {
          (element as HTMLImageElement).src = lazySrc;
          element.classList.add('loaded');
        }
        
        this.loadedImages.add(imageId);
        console.log('Lazy loaded image:', imageId);
      };
      img.src = lazySrc;
    }
  }
  
  disconnect() {
    this.observer.disconnect();
  }
}

// Infinite scroll implementation
class InfiniteScroll {
  private observer: IntersectionObserver;
  private isLoading = false;
  private onLoadMore: () => Promise<void>;
  
  constructor(onLoadMore: () => Promise<void>) {
    this.onLoadMore = onLoadMore;
    this.observer = createIntersectionObserver(null, {
      rootMargin: '100px', // Trigger 100px before reaching the sentinel
      thresholds: [0]
    });
  }
  
  observe(sentinelSelector: string) {
    this.observer.observe(sentinelSelector, async (result) => {
      if (result.intersectionRatio > 0 && !this.isLoading) {
        this.isLoading = true;
        
        try {
          await this.onLoadMore();
        } catch (error) {
          console.error('Failed to load more content:', error);
        } finally {
          this.isLoading = false;
        }
      }
    });
  }
  
  disconnect() {
    this.observer.disconnect();
  }
}

// Usage
const lazyLoader = new LazyLoader();
lazyLoader.observeImages();

const infiniteScroll = new InfiniteScroll(async () => {
  console.log('Loading more content...');
  // Load more content logic here
});
infiniteScroll.observe('#load-more-sentinel');

// Element animation on scroll
function observeForAnimations() {
  const observer = createIntersectionObserver(null, {
    thresholds: [0.1, 0.5, 0.9]
  });
  
  observer.observe('.animate-on-scroll', (result) => {
    const element = document.getElementById(result.id);
    if (!element) return;
    
    if (result.intersectionRatio > 0.1) {
      element.classList.add('fade-in');
    }
    
    if (result.intersectionRatio > 0.5) {
      element.classList.add('slide-up');
    }
    
    if (result.intersectionRatio > 0.9) {
      element.classList.add('fully-visible');
    }
  });
}

Media Query Observer

Monitor CSS media query changes for responsive design handling.

/**
 * Create media query observer for responsive design
 * @returns MediaQueryObserver instance
 */
function createMediaQueryObserver(): MediaQueryObserver;

interface MediaQueryObserver {
  /** Start observing media query changes */
  observe(descriptor: MediaQueryDescriptor, callback: MediaQueryCallback): void;
  /** Stop observing media query changes */
  unobserve(): void;
  /** Disconnect the observer */
  disconnect(): void;
}

interface MediaQueryDescriptor {
  /** CSS media query string */
  minWidth?: number;
  maxWidth?: number;
  orientation?: 'portrait' | 'landscape';
}

interface MediaQueryResult {
  /** Whether the media query matches */
  matches: boolean;
}

type MediaQueryCallback = (result: MediaQueryResult) => void;

Usage Examples:

import { createMediaQueryObserver } from "@tarojs/taro-h5";

// Responsive design handling
class ResponsiveManager {
  private observer: MediaQueryObserver;
  private breakpoints = {
    mobile: 768,
    tablet: 1024,
    desktop: 1200
  };
  
  constructor() {
    this.observer = createMediaQueryObserver();
    this.setupBreakpointObservers();
  }
  
  private setupBreakpointObservers() {
    // Mobile breakpoint
    this.observer.observe(
      { maxWidth: this.breakpoints.mobile - 1 },
      (result) => {
        if (result.matches) {
          this.handleMobileView();
        }
      }
    );
    
    // Tablet breakpoint
    this.observer.observe(
      { 
        minWidth: this.breakpoints.mobile, 
        maxWidth: this.breakpoints.tablet - 1 
      },
      (result) => {
        if (result.matches) {
          this.handleTabletView();
        }
      }
    );
    
    // Desktop breakpoint  
    this.observer.observe(
      { minWidth: this.breakpoints.desktop },
      (result) => {
        if (result.matches) {
          this.handleDesktopView();
        }
      }
    );
    
    // Orientation changes
    this.observer.observe(
      { orientation: 'portrait' },
      (result) => {
        this.handleOrientationChange(result.matches ? 'portrait' : 'landscape');
      }
    );
  }
  
  private handleMobileView() {
    console.log('Switched to mobile view');
    document.body.classList.add('mobile');
    document.body.classList.remove('tablet', 'desktop');
  }
  
  private handleTabletView() {
    console.log('Switched to tablet view');
    document.body.classList.add('tablet');
    document.body.classList.remove('mobile', 'desktop');
  }
  
  private handleDesktopView() {
    console.log('Switched to desktop view');
    document.body.classList.add('desktop');
    document.body.classList.remove('mobile', 'tablet');
  }
  
  private handleOrientationChange(orientation: 'portrait' | 'landscape') {
    console.log('Orientation changed to:', orientation);
    document.body.classList.toggle('portrait', orientation === 'portrait');
    document.body.classList.toggle('landscape', orientation === 'landscape');
  }
  
  disconnect() {
    this.observer.disconnect();
  }
}

// Usage
const responsiveManager = new ResponsiveManager();

// Simple media query observation
function observeScreenSize() {
  const observer = createMediaQueryObserver();
  
  observer.observe({ maxWidth: 600 }, (result) => {
    if (result.matches) {
      console.log('Small screen detected');
      // Adjust UI for small screens
    } else {
      console.log('Large screen detected');
      // Adjust UI for large screens
    }
  });
}

Advanced Usage Patterns

Complex DOM query scenarios and performance optimization techniques.

// Performance-optimized DOM queries
class DOMQueryManager {
  private queryCache = new Map<string, any>();
  private cacheTimeout = 1000; // 1 second cache
  
  async queryWithCache(selector: string, fields: FieldsOptions): Promise<any> {
    const cacheKey = `${selector}:${JSON.stringify(fields)}`;
    const cached = this.queryCache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
      return cached.data;
    }
    
    const data = await this.performQuery(selector, fields);
    
    this.queryCache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });
    
    return data;
  }
  
  private performQuery(selector: string, fields: FieldsOptions): Promise<any> {
    return new Promise((resolve) => {
      const query = createSelectorQuery();
      
      query.select(selector)
        .fields(fields, (result) => {
          resolve(result);
        });
      
      query.exec();
    });
  }
  
  clearCache() {
    this.queryCache.clear();
  }
}

// Batch DOM operations
class BatchDOMOperations {
  private operations: (() => void)[] = [];
  private isProcessing = false;
  
  addOperation(operation: () => void) {
    this.operations.push(operation);
    this.scheduleExecution();
  }
  
  private scheduleExecution() {
    if (this.isProcessing) return;
    
    this.isProcessing = true;
    
    requestAnimationFrame(() => {
      // Execute all queued operations
      while (this.operations.length > 0) {
        const operation = this.operations.shift();
        if (operation) {
          try {
            operation();
          } catch (error) {
            console.error('Batch DOM operation failed:', error);
          }
        }
      }
      
      this.isProcessing = false;
    });
  }
  
  measureElements(selectors: string[]): Promise<BoundingClientRect[]> {
    return new Promise((resolve) => {
      const query = createSelectorQuery();
      const results: BoundingClientRect[] = [];
      
      selectors.forEach((selector, index) => {
        query.select(selector)
          .boundingClientRect((rect) => {
            results[index] = rect;
          });
      });
      
      query.exec(() => {
        resolve(results);
      });
    });
  }
}

// Usage examples
const queryManager = new DOMQueryManager();
const batchOps = new BatchDOMOperations();

// Cached queries
const elementInfo = await queryManager.queryWithCache('#my-element', {
  rect: true,
  properties: ['innerHTML']
});

// Batch measurements
const measurements = await batchOps.measureElements([
  '#header',
  '#content', 
  '#footer'
]);

console.log('Element measurements:', measurements);

Types

interface DOMRect {
  x: number;
  y: number;
  width: number;
  height: number;
  top: number;
  right: number;
  bottom: number;
  left: number;
}

type MediaQueryString = string;
type CSSSelector = string;

interface QueryResult {
  id: string;
  dataset: Record<string, any>;
  [key: string]: any;
}

Install with Tessl CLI

npx tessl i tessl/npm-tarojs--taro-h5

docs

canvas.md

core-framework.md

device.md

dom-query.md

index.md

location.md

media.md

navigation.md

network.md

storage.md

system-info.md

ui-interactions.md

tile.json