Three‑State Drawer, Custom Navigation, and Common Pitfalls in Taro.js WeChat Mini‑Program Development
This article shares practical solutions and troubleshooting techniques for building WeChat mini‑programs with Taro.js, covering a three‑state draggable drawer, multi‑page swipe navigation, custom navigation bars, async cleanup, DOM timing, polyfill size, audio handling, scroll‑view quirks, canvas limitations, and image cropping methods.
Introduction
Recently I have been developing a WeChat mini‑program using Taro.js. Because mini‑programs differ greatly from the web, many pitfalls were encountered.
Architecture
Mini‑programs are essentially a restricted hybrid: JavaScript runs in a logic layer without HTML and communicates with the view and native layers via APIs. The logic and view layers run on separate isolated threads, giving the platform strong control and reducing risk, but many web capabilities are trimmed.
Performance
The performance is comparable to React Native; every interaction must pass through the native bridge, which is slower than pure native or web.
Main Content
Solution: Three‑State Drawer Drag Switching
Requirement
Implement a drawer overlay with three states:
1. Height = half of screen
2. Full‑screen
3. Closed
Switch between states by dragging.Approaches and Issues
Touch solution: use onTouchStart / onTouchMove / onTouchEnd / onTouchCancel to capture gestures; determine state on onTouchEnd . Drawback: frequent setState causes jank during onTouchMove .
Scroll solution: use scrollview with onScroll ; animate via scroll effect; determine state on onScrollEnd . Drawback: scrollend fires when the gesture stops, not when inertia ends, leading to inaccurate positions.
WeChat mini‑program wxs event: Taro.js does not yet support it; consider mixing native components.
Solution: Multi‑Page Horizontal Swipe Switching
Approach
Use the built‑in Swiper component, bind the change event, keep only three pages of data (previous, current, next). When paging, replace the three pages accordingly. The effect is smooth and performance acceptable.
Solution: Custom Navigation Bar
Approach
Set navigationStyle: "custom" to hide the default navigation bar.
In app.ts obtain status‑bar and capsule layout to compute custom bar dimensions.
Create a custom navigation component that reads the current route via getCurrentPages and decides which left‑button to show (back/home/none).
Problem
After navigating back from the home page, an extra back button appears because setState during navigation triggers a re‑render that uses the new route.
Solution: memoise the button state with useMemo so it is cached on the first render.
Note
From client version 6.7.2, navigationStyle: "custom" does not affect the web‑view component.
Problem: Asynchronous Work Continues After Page Unload
This issue is similar to SPA behavior: asynchronous tasks started on page A keep running after returning to the previous page.
Solution
Clear asynchronous operations when the page is unloaded.
Set a flag on unload and check it before executing async callbacks.
Problem: Timing of DOM Node Access in Mini‑Program
Unlike web React, the first useEffect runs before the DOM nodes are mounted; the nodes become available only after onReady .
Lifecycle order: onLoad → onShow → useEffect → onReady
Solution
Delay with setTimeout and poll.
Use wx.nextTick and poll until the node is obtained.
Access the node in onReady / useReady .
Problem: After Route Jump (Async) Cannot Obtain Page DOM Node
After navigation, callbacks of SelectorQuery.exec do not fire in asynchronous contexts such as timers, promises, or component lifecycles.
Taro.navigateTo({
url: '/'
});
setTimeout(() => {
const query = Taro.createSelectorQuery();
query
.select('#a')
.fields({ node: true })
.exec((res) => {
console.log(res[0].node);
});
}, 0);Solution
Call SelectorQuery synchronously during navigation.
Delay the navigation with setTimeout .
Handle the query in onShow .
Problem: Missing Global Methods (e.g., atob/btoa) in Mini‑Program Environment
Solution
Implement the methods manually.
Use a third‑party polyfill.
Problem: Using require('crypto') in Taro.js Causes Large Polyfill Size
Webpack pulls in a full crypto polyfill chain (bn.js → browserify‑sign → crypto‑browserify → node‑libs‑browser), inflating the bundle by ~600 KB.
Solution
Avoid require('crypto') when possible.
Replace it with a lightweight third‑party library.
Problem: InnerAudioContext onTimeUpdate Not Triggered After Seek or Replay
Root Cause
Audio loading disables onTimeUpdate ; seeking triggers a reload.
Solution
Access innerAudioContext.paused in the onCanPlay callback to reactivate onTimeUpdate .
audioManager.onCanplay(() => {
audioManager.paused;
});Audio Playback Options in Mini‑Program
Audio (deprecated, limited features)
InnerAudioContext – normal audio playback, multiple instances allowed.
BackgroundAudioManager – global singleton, continues playing in background.
WebAudioContext – beta, similar to Web Audio API.
MediaAudioPlayer – plays audio output from VideoDecoder .
Differences between InnerAudioContext and BackgroundAudioManager :
Multiple instances vs. single global instance.
Background pause behavior vs. continued playback with UI controls.
Background manager requires title, cover, album, artist metadata.
Problem: scroll-view Scrolling Issues
Adaptive height does not enable vertical scrolling; an explicit height is required.
If the first direct child of scroll-view has margin-top , scrolling works even with a single line. Use padding-top or add an empty element with height as a workaround.
Problem: Caution When Using Canvas
WeChat’s canvas support has several known issues:
OffScreenCanvas version support does not match documentation.
Images created with Canvas.createImage do not fire onload on iOS.
Solution: Image Cropping Method
Given an original image and four‑corner coordinates, the goal is to crop the quadrilateral region as a separate image.
Because canvas support is unreliable, a CSS‑based workaround is used:
Calculate the bounding rectangle of the quadrilateral.
Use background-image , background-position , and background-size to display the cropped area.
Restrict the element size to the bounding rectangle.
Two important notes:
The result is a rectangle, not the original quadrilateral shape.
The container size must be computed to fit the maximum width.
ByteDance Dali Intelligent Technology Team
Technical practice sharing from the ByteDance Dali Intelligent Technology Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.