--- url: /guide/ui/elements-components.md --- # Composing Elements A Lynx page may contain various visual elements such as text and images, presented in different layouts to create diverse page styles. This section aims to help everyone understand how to construct the most basic views. ## Element tag: UI Building Blocks Lynx defines content and structure using a markup language, with the most basic unit being an [element tag](/guide/spec.md#element-tag). The concept of an element tag is similar to [HTML elements](https://developer.mozilla.org/en-US/docs/Glossary/Element), which can be used to encapsulate different parts of the content to present or operate in a specific manner. Unlike HTML, Lynx uses some unique element tags such as [``](/api/elements/built-in/view.md), [``](/api/elements/built-in/text.md), and [``](/api/elements/built-in/image.md) to display different content. For example, in Lynx, the following source code can be used to display a piece of text: ```html Hello Lynx ``` ### Anatomy of an Element tag The basic usage of element tags is very similar to [HTML elements](https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content#anatomy_of_an_html_element): ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/elements_tags.png) Each element tag consists of the following parts: 1. **Start tag**: Includes the name of the element tag (in this case, text) surrounded by angle brackets, indicating where the element tag begins. 2. **End tag**: Similar to the start tag but includes a forward slash before the element tag's name, indicating where the element tag ends. 3. **Content**: The content of the element tag, which for the `` element tag is the text itself. Combining the start tag, end tag, and content forms a complete element tag. ### Attributes Each element tag has its own attributes that can be set by adding attribute names and values within the element tag's tag to describe its behavior and appearance. For example, each element tag can use attributes such as [`style`](/api/elements/built-in/view.md#style) and [`class`](/api/elements/built-in/view.md#class) to set background, border-radius, shadow styles, and support some CSS syntax. The following code sets the background color of an element tag to red: ```html Hello Lynx ``` In this example, `style` is the attribute name, and `background:red` is the attribute value. For more attributes, refer to the [API Reference](/api/elements/built-in/view.md). ### Empty Element tags Some element tags do not have content, such as the `` element tag: ```html ``` It does not use an `` end tag, nor does it have any content inside because the `` element tag uses an attribute `src` to display an image rather than content. ### Nested Element tags Element tags can be nested within other element tags. For example, multiple `` element tags can be nested inside a `` element tag: ```html Hello Lynx ``` ### Element Tree The element tags in the source code will be parsed by the Lynx engine at runtime and translated into [elements](/guide/spec.md#element) for rendering. Nested element tags will form a tree composed of elements, which we refer to as the [element tree](/guide/spec.md#element-tree). We rely on this tree structure to build and manage more complex interfaces. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/elements_tree.png) ## Built-in Elements The Lynx Engine comes with some built-in elements by default to help you quickly build pages. ### View The `` is the most basic element, commonly used to wrap other elements and carry some drawing capability. For example, the following code sets the entire view's background color to gray and adds some padding within the view: ```html Hello Lynx ``` ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/elements_hello_lynx_gray.png) ### Text As mentioned earlier, the `` element is used to display text content. For instance, the following code can be used to display a piece of text: ```html Hello Lynx ``` ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/elements_hello_lynx.png) ### Image The `` element is used to display images. For example, the following code can be used to display an image: ```html ``` ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/elements_lynx_logo.png) ### More Built-in Elements For all built-in Lynx elements, refer to [Built-in Elements Documentation](/api/elements/built-in/view.md). ### XElement XElement is an extended native elements library maintained by the Lynx team. It provides richer component capabilities, enabling faster adoption of Lynx in production environments and enhancing the vibrancy of the Lynx ecosystem. To use XElement, it needs to be integrated into your native project first. See [Integrate with Existing Apps](/guide/start/integrate-with-existing-apps.md) for setup instructions. ## Behind the Elements: Native Rendering ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/elements_2_na.png) Lynx elements are designed to be platform-agnostic. They are rendered natively by the Lynx Engine into the UI primitives for each platforms, such as iOS and Android views, or HTML elements (including [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)) on the Web. Lynx enables cross-platform application development based on the web technology, with its core being the establishment of a unified rendering system through element abstraction. Understanding the mapping relationship between the native views of the platform and Lynx elements is crucial to mastering the design concepts of elements within this framework. Below are some of the built-in elements and their corresponding concepts or analogues in different platforms: | Element | Android | iOS | HarmonyOS | Web analogy | Description | | :------------------------------------------------------- | :----------------------- | :-------------------------------- | :----------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------ | | [``](/api/elements/built-in/view.md) | `ViewGroup` | `UIView` | `Stack` | Non-scrollable `
` | Basic view container, often used for layout capabilities, stylization, and wrapping other elements. | | [``](/api/elements/built-in/text.md) | `TextView` | `UITextView` | `Text` | `

` | Used for displaying text content. Specific text styles can be aligned. | | [``](/api/elements/built-in/image.md) | `ImageView` | `UIImageView` | `Image` | `` | Used for displaying different types of images, including web images, static resources, and local disk images. | | [``](/api/elements/built-in/scroll-view.md) | `ScrollView` | `UIScrollView` | `Scroll` | `

` with `overflow:scroll` | Basic scrollable element that supports horizontal and vertical scrolling. Allows users to scroll to display more content. | | [``](/api/elements/built-in/list.md) | `RecyclerView` | `UICollectionView` | `List` | None | High-performance scrollable element that reduces memory pressure through lazy loading and view reuse. | | [``](/api/elements/built-in/page.md) | `ViewRootImpl` of a page | `UIViewController.view` of a page | A custom component decorated with `@Entry` | Non-resizable `` | Root node of a page, usually doesn't need to be added manually. | ## Extending with Custom Elements If built-in elements can't meet your needs, you can expand Lynx's capabilities by implementing native elements customarily. This is one of Lynx's powerful features. For more details, refer to [Extending Native Elements Documentation](/guide/custom-native-component.md). ## Components: Composition of Elements In more complex Lynx view structures, various types of elements are often nested and combined layer by layer to form richer and more diverse interface units. This is the core idea of component-based development in front-end frameworks: achieving modular construction of interfaces through reusable encapsulation units. In ReactLynx, we follow the React development paradigm. By using a function and JSX to assemble the elements and define a component, its design philosophy and basic principles follow the [React Component Design Documentation](https://react.dev/learn/describing-the-ui). For example: **This is an example below: composing-elements** **Entry:** `src/App.tsx` **Bundle:** `dist/main.lynx.bundle` | Web: `dist/main.web.bundle` ```tsx {7-10} // Copyright 2024 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. export const App = ({ src }: { src: string }) => { return ( Hello Lynx ); }; ``` *** In the next chapter, we will add more elaborate styles to the interface. --- url: /guide/ui/styling.md --- # Styling with CSS Cascading Style Sheets (CSS) are used to style and layout Lynx pages. For example, you can change the font, color, size, and position of the content, split the content into multiple columns, or add animations and other decorative elements to make your pages more vivid and interesting. In addition, Lynx provides numerous properties starting with `-x-` to help you achieve style design more easily. The following tutorial will demonstrate how to add styles to elements using CSS. :::tip If you have no basic knowledges about CSS, you can go through the [guidance](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics) from web docs. ::: ## Selectors and inline styles You can use selectors and inline styles to set values to element's properties. Such as using `class` attribute with a class selector: The following example set the background property of the element whose `class` has 'bg-gradient'. **This is an example below: css** **Entry:** `src/class_guide` **Bundle:** `dist/class_guide.lynx.bundle` | Web: `dist/class_guide.web.bundle` ```tsx {16} import { root } from "@lynx-js/react"; import "./index.css"; function App() { return ( ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` And you can also set the element's properties via `style` attribute directly. In the example before, we use `style` to change the element's position and size. - [Learn more about styling properties](/guide/styling/appearance.md) - [Property API references](/api/css/properties.md) - [Selector API references](/api/css/selectors.md) ### Nesting With nesting syntax, you can declare classes in an easier way. You can get this in [Sass](/rspeedy/styling.md#%E4%BD%BF%E7%94%A8-sass), which is already supported in ReactLynx, or other [post css plugins](/rspeedy/styling.md#using-postcss), such as [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting). ```scss .a { background: red; &-b { border-radius: 30px; } } /* equals */ .a { background: red; } .a-b { border-radius: 30px; } ``` ## CSS cascading The [Cascade](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Cascade) specification defines which value takes effect when multiple selectors applied to the same element, and they got duplicate properties with different values. For example, properties set by `style` attribute covers those set by style rules (e.g class selector), class with higher [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Specificity) covers those lower ones. **This is an example below: css** **Entry:** `src/cascade_guide` **Bundle:** `dist/cascade_guide.lynx.bundle` | Web: `dist/cascade_guide.web.bundle` ```css .bg-gradient { background: radial-gradient( circle at top left, rgb(255, 53, 26), rgb(0, 235, 235) ); } .bg-color { background: rgb(255, 53, 26); } ``` In the case above,both two selectors, `bg-gradient` and `bg-color` are taking effect on the ``,and they are both changing the `background` property. Following the specification of the cascade, the class appears later in the document covers the earlier one, thus the rectangle should be red. ## CSS Variables and Opt-in Inheritance Lynx supports [CSS custom properties](/api/css/properties/css-variable.md) (CSS variables) and opt-in CSS inheritance for dynamic styling and theming. CSS custom properties are inherited by default, while regular (non-custom) CSS properties require explicit configuration. See the Theming guide for details: - [Switch themes using CSS descendant selectors](/guide/styling/custom-theming.md#using-css-descendant-selectors-to-switch-themes) - [Switch themes using CSS custom properties](/guide/styling/custom-theming.md#using-css-variables-to-switch-themes) - [Configure Lynx's opt-in CSS inheritance](/guide/styling/custom-theming.md#leveraging-css-inheritance-as-needed) --- url: /guide/ui/layout/index.md --- # Understanding Layout Lynx provides: - Properties such as [`width`], [`height`], [`margin`], and [`padding`] are used to describe the size of elements. - The [`display`] property, along with [linear layout], [flexible box layout], [grid layout], and [relative layout], are used for laying out elements. - The aligning properties, including [`align-items`], [`justify-content`] and etc., are used for aligning elements. - The [`position`] property, along with [`left`], [`right`], [`top`], [`bottom`] properties, are used to position elements. - [`direction`] and [logical properties] are used to support the internationalization of layouts. For the layout properties supported by Lynx with the same name in the Web, the behavior of these properties will be consistent with those in the Web. However, there is a difference in the design concept between Lynx layout and Web layout: Web layout is primarily text-based. While Lynx layout is based on elements (``, ``, ``, etc.). In other words, elements in Lynx are all [block-level elements](https://developer.mozilla.org/en/docs/Glossary/Block-level_content). The following tutorial will show you how to complete the layout of elements in Lynx. ## Sizing the Elements You can use [`width`], [`height`], [`max-width`], [`min-width`], [`max-height`], [`min-height`], [`margin`], [`padding`], and [`border-width`] to describe the size and box model of an element. These properties support various [length units] such as `px`, `%`, `vh` and etc. Additionally, `width` and `height` support [`max-content`] and [`fit-content`] for sizing elements according to their content. **This is an example below: layout** **Entry:** `src/sizing` **Bundle:** `dist/sizing.lynx.bundle` | Web: `dist/sizing.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const SizingExample = () => { return ( Auto Sized Element Size Decided by Content Size as Specified Specify Size with Percentage Limited By Max Size Expanded By Min Size ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` `box-sizing` is `border-box` by default and Lynx does not exhibit the behavior of margin collapsing.By default, the size properties of Lynx, such as `width`, `height`, and `max-width`, describe the size of the border box. This is inconsistent with the default behavior of the Web. Lynx does not exhibit the behavior of [margin collapsing](https://developer.mozilla.org/en/docs/Web/CSS/CSS_box_model/Mastering_margin_collapsing) as in the Web. ## Layout the Elements The [``] element can be used for laying out child elements, and by setting the [`display`] property on the `` element, you can control the way it lays out its child elements. The display property supports five values: `linear`, `flex`, `grid`, `relative` and `none`. ### Linear Layout If you want to simply arrange the elements in order, you can set the [`display`] property to linear and use Lynx's default layout, which is the [linear layout]. The Linear layout (inspired by Android's [Linear Layout](https://developer.android.com/develop/ui/views/layout/linear)) can arrange the elements in order according to the direction you declare. **This is an example below: layout** **Entry:** `src/linear` **Bundle:** `dist/linear.lynx.bundle` | Web: `dist/linear.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const LinearExample = () => { return ( column item 1 column item 2 column item 3 row item 1 row item 2 row item 3 ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Flexible Box Layout If you need to make the size of child elements adapt to the space of the parent element (such as expanding child elements to fill the unused space or shrinking child elements to avoid overflow), you can set the [`display`] property to `flex` and use the [flexible box layout]. The flexible box layout in Lynx is consistent with the one in Web. **This is an example below: layout** **Entry:** `src/flex` **Bundle:** `dist/flex.lynx.bundle` | Web: `dist/flex.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const FlexExample = () => { return ( item grow to fill large item shrink to fit ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Grid Layout If you want to arrange multiple elements alternately in both vertical and horizontal directions to form a two-dimensional layout, you can set the [`display`] property to `grid` and use the [grid layout], a layout in the Web that can divide the space into a two-dimensional grid and place elements in the specified rows and columns. Lynx supports a subset of its features. **This is an example below: layout** **Entry:** `src/grid` **Bundle:** `dist/grid.lynx.bundle` | Web: `dist/grid.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const GridExample = () => { return ( grid-template-columns: 1fr 100px 2fr; grid-template-rows: 1fr 1fr; span 2 TWO THREE span 2 grid-template-columns: 20% max-content minmax(50px, max-content); 20% NO WRAP! min-width:50px, will fit the container grid-template-columns: 1fr 1fr; grid-template-rows: 1fr max-content 1fr; span 2 max-content: will take the maximum size of the items as the row size THREE columnStart: -2 ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Relative Layout If you want to describe the layout through the relative position relationship between elements, you can set the [`display`] property to `relative` and use the [relative layout]. The relative layout(inspired by Android's [Relative Layout](https://developer.android.com/develop/ui/views/layout/relative)) can declare the layout by describing the position relationship between elements (for example, one element is located to the left of another element). **This is an example below: layout** **Entry:** `src/relative_layout` **Bundle:** `dist/relative.lynx.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const RelativeExample = () => { return ( ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Text and Inline Layout In Lynx, text cannot be directly inserted into the [``] element, and the [`display`] property of Lynx does not support the values of `inline` and `block`. The [``] element can complete text display and the inline layout of elements. For details, please refer to [Text Typography](/guide/styling/text-and-typography.md). ## Aligning the Elements You can align the elements laid out by a [``] with [`align-items`], [`align-self`], [`justify-content`], [`align-content`], [`justify-items`] and [`justify-self`] properties. **This is an example below: layout** **Entry:** `src/align` **Bundle:** `dist/align.lynx.bundle` | Web: `dist/align.web.bundle` ```tsx {10,13,18,29} import { root } from "@lynx-js/react"; import "./index.scss"; const AlignExample = () => { return ( linear item 1 linear item 2 linear item 3 flex item 1 flex item 2 flex item 3 flex item 4 grid item 1 grid item 2 grid item 3 ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Offsetting and Absolute Positioning You can offset the elements with [`top`], [`left`], [`bottom`], and [`right`] properties. And you can make elements absolutely positioned with [`position`] property. **This is an example below: layout** **Entry:** `src/position` **Bundle:** `dist/position.lynx.bundle` | Web: `dist/position.web.bundle` ```tsx {12,26-28,43} import { root } from "@lynx-js/react"; import "./index.scss"; const PositionExample = () => { return ( 1 2 3 1 ABS 3 1 FIXED 3 ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Internationalization You can use the [`direction`] property and [logical properties] to make your page support both languages written from left to right (such as Chinese) and languages written from right to left (such as Arabic). [logical properties] refer to a series of properties like `XX-inline-start` and `XX-inline-end` (such as [`inset-inline-start`]). Enable CSS Inheritance and Logical PropertiesPlease refer to [CSS Inheritance](/guide/styling/custom-theming.md#how-to-enable-css-inheritance), using [`enableCSSInheritance`] to enable `direction` to be inheritable, and using logical properties depends on the `direction` value. **This is an example below: layout** **Entry:** `src/direction` **Bundle:** `dist/direction.lynx.bundle` | Web: `dist/direction.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const DirectionExample = () => { return ( 1 2 3 1 2 3 ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` [`width`]: /api/css/properties/width.md [`height`]: /api/css/properties/height.md [`min-width`]: /api/css/properties/min-width.md [`min-height`]: /api/css/properties/min-height.md [`max-width`]: /api/css/properties/max-width.md [`max-height`]: /api/css/properties/max-height.md [`flex-grow`]: /api/css/properties/flex-grow.md [`flex-shrink`]: /api/css/properties/flex-shrink.md [`direction`]: /api/css/properties/direction.md [`display`]: /api/css/properties/display.md [`margin`]: /api/css/properties/margin.md [`padding`]: /api/css/properties/padding.md [`position`]: /api/css/properties/position.md [`border-width`]: /api/css/properties/border-width.md [`inset-inline-start`]: /api/css/properties/inset-inline-start.md [`top`]: /api/css/properties/top.md [`bottom`]: /api/css/properties/bottom.md [`max-content`]: /api/css/data-type/max-content.md [`fit-content`]: /api/css/data-type/fit-content.md [`justify-self`]: /api/css/properties/justify-self.md [`justify-items`]: /api/css/properties/justify-items.md [`align-self`]: /api/css/properties/align-self.md [`align-content`]: /api/css/properties/align-content.md [`justify-content`]: /api/css/properties/justify-content.md [`align-items`]: /api/css/properties/align-items.md [`left`]: /api/css/properties/left.md [`right`]: /api/css/properties/right.md [``]: /api/elements/built-in/view.md [``]: /api/elements/built-in/text.md [length units]: /api/css/data-type/length-percentage.md [linear layout]: /guide/ui/layout/linear-layout.md [relative layout]: /guide/ui/layout/relative-layout.md [flexible box layout]: /guide/ui/layout/flexible-box-layout.md [grid layout]: /guide/ui/layout/grid-layout.md [logical properties]: https://developer.mozilla.org/en/docs/Web/CSS/CSS_logical_properties_and_values [`enableCSSInheritance`]: /api/rspeedy/react-rsbuild-plugin.pluginreactlynxoptions.enablecssinheritance.md#pluginreactlynxoptionsenablecssinheritance-property --- url: /guide/ui/layout/linear-layout.md --- # Linear Layout If you want to arrange children sequentially without dealing with the complexities of [flexible box](/guide/ui/layout/flexible-box-layout.md) and [grid](/guide/ui/layout/grid-layout.md) layouts (such as shrink and placement issues), consider using **linear layout**. This layout is inspired by [linear layout](https://developer.android.com/develop/ui/views/layout/linear) in Android. The default layout direction of a linear layout is vertical. You can also use Web's alignment properties such as [`align-items`](/api/css/properties/align-items.md), [`align-self`](/api/css/properties/align-self.md), and [`justify-content`](/api/css/properties/justify-content.md) with this layout. For the supported properties, please refer to the [Reference section](#reference). ## How to Build a Linear Layout? ### Step 1: Apply `display: linear` To implement a linear layout, modify the `display` property of the parent element to use a linear layout for its children. ```css display: linear; ``` ### Step 2: Set the Layout Direction A linear layout arranges elements along the main and cross axes, similar to a [flexible box layout](/guide/ui/layout/flexible-box-layout.md). The **main axis** refers to the direction in which elements are aligned, whereas the **cross axis** is perpendicular to it. You can adjust the layout direction by altering the [`linear-direction`](/api/css/properties/linear-direction.md) property of the parent container, akin to the [`flex-direction`](/api/css/properties/flex-direction.md) property in flexible box layouts. By default, `linear-direction` is set to `column`. ```css linear-direction: column; ``` ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/linear_main_axis.png) ### Step 3: Align Children Along the Main Axis To control the position of child elements along the main axis, use the [`justify-content`](/api/css/properties/justify-content.md) property. In the demo below, the main axis is vertical. **This is an example below: layout** **Entry:** `src/linear_justify_content` **Bundle:** `dist/linear_justify_content.lynx.bundle` | Web: `dist/linear_justify_content.web.bundle` ```tsx {9,16,23} import { root } from "@lynx-js/react"; import "./index.scss"; const LinearJustifyContentExample = () => { return ( justify-content: start justify-content: end justify-content: center justify-content: space-between ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Step 4: Align Children Along the Cross Axis To align items within a container along the cross axis, apply [`align-items`](/api/css/properties/align-items.md) to the container or [`align-self`](/api/css/properties/align-items.md) to children. In the example below, the cross axis is vertical, with `align-items: center` used in the container to center children along this axis. **This is an example below: layout** **Entry:** `src/linear_align_items` **Bundle:** `dist/linear_align_items.lynx.bundle` | Web: `dist/linear_align_items.web.bundle` ```tsx {27} import { root } from "@lynx-js/react"; const LinearAlignItemsExample = () => { return ( align-items: center ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` By adjusting the `align-self` property on a child, you can override `align-items` behavior, as shown with the first block below the parent element. **This is an example below: layout** **Entry:** `src/linear_align_self` **Bundle:** `dist/linear_align_self.lynx.bundle` | Web: `dist/linear_align_self.web.bundle` ```tsx {17,27} import { root } from "@lynx-js/react"; const LinearAlignSelfExample = () => { return ( ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ::: info Please note that when the cross-axis size of the parent element (such as the `width` when `linear-direction: column`) is set, and if the size of the child element in this direction is not specified (or `auto`), the children's size along the cross-axis will expand to fill the container. ::: ### Step 5: Specify Dynamic Sizes Along the Main Axis Linear layout allows for the [`linear-weight`](/api/css/properties/linear-weight.md) attribute, enabling adaptive sizing along the main axis based on assigned weight ratios and available space. Set the [`linear-weight`](/api/css/properties/linear-weight.md) for each child to define its size share along the main axis. The parent container adjusts every child's dimensions to fit proportions derived from their respective weight values relative to available space. **This is an example below: layout** **Entry:** `src/linear_weight` **Bundle:** `dist/linear_weight.lynx.bundle` | Web: `dist/linear_weight.web.bundle` ```tsx {33,42,51} import { root } from "@lynx-js/react"; const LinearAlginItemsExample = () => { return ( linear-weight: 0.5 : 2 : 0.5 ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` In the above example, `linear-weight` specifies a scale value without unit representing the amount of space available for each child element along the main axis. The three child elements will have a 0.5 : 2 : 0.5 ratio of the main axis space. ## Reference Currently, the linear layout supports the following layout properties: - **Specific CSS Properties** - [`linear-direction`](/api/css/properties/linear-direction.md) - [`linear-weight`](/api/css/properties/linear-weight.md) - **Alignment Properties** - [`justify-content`](/api/css/properties/justify-content.md) - [`align-items`](/api/css/properties/align-items.md) - [`align-self`](/api/css/properties/align-self.md) - **Other Properties** Other properties such as [`order`](/api/css/properties/order.md), [`aspect-ratio`](/api/css/properties/aspect-ratio.md), etc., are not listed individually here; for specific property support, refer to the [API Reference](/api/css/properties.md). --- url: /guide/ui/layout/flexible-box-layout.md --- # Flexible Box Layout If you need to make the size of child elements adapt to the space of the parent element (such as expanding child elements to fill the unused space or shrinking child elements to avoid overflow), you can set the [`display: flex`](/api/css/properties/display.md) property to the parent element and use the **flexible box layout**. ::: info For more information, please refer to the [CSS Flexible Box Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout) on MDN. Lynx supports common flexible box layout properties and in most cases aligns with Web standards. For the supported properties, please refer to the [Reference section](#reference). ::: **The following examples show typical features of the flexible box layout.** ## Typical Features ### Filling the Parent Element with `flex-grow` The [`flex-grow`] property helps you allocate the remaining space of the parent element to the size of the sub-elements based on the weight declared by `flex-grow`. **This is an example below: layout** **Entry:** `src/flex_grow` **Bundle:** `dist/flex_grow.lynx.bundle` | Web: `dist/flex_grow.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const FlexGrowExample = () => { return ( flex-direction: column flex-grow: 1 flex-grow: 2 100px ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Shrinking Child Elements with `flex-shrink` When the child elements are about to overflow the parent element, the child elements can shrinked according to the weight declared by [flex-shrink](/api/css/properties/flex-shrink.md) to fit the size of the parent element. **This is an example below: layout** **Entry:** `src/flex_shrink` **Bundle:** `dist/flex_shrink.lynx.bundle` | Web: `dist/flex_shrink.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const FlexShrinkExample = () => { return ( flex-direction: column flex-shrink: 0 height 300px flex-shrink: 1 height shrinks from 300px to fit container ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` Lynx differs from Web in the minimum value for shrinking sub-elements.Lynx currently does not support [`min-content`](https://developer.mozilla.org/en-US/docs/Web/CSS/min-content), and therefore treats it temporarily as `0px`. This means that while the Web can ensure sub-elements do not shrink below their minimum content width when fitting the parent element size, Lynx cannot guarantee this at present. Code```html
``` Inconsistent behavior between Web and Lynx![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/layout/layout_min_content.png) ### Wrapping with `flex-wrap` The [`flex-wrap`] property allows content that doesn't fit on a single line to be displayed on subsequent lines. This attribute specifies whether flex elements are shown in a single or multiple lines. When allowed to wrap, this attribute can control the stacking direction of the lines. **This is an example below: layout** **Entry:** `src/flex_wrap` **Bundle:** `dist/flex_wrap.lynx.bundle` | Web: `dist/flex_wrap.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const FlexWrapExample = () => { return ( flex-direction: row; flex-wrap: wrap; Item 1 Item 2 Item 3 flex-direction: row; flex-wrap: nowrap; Item 1 Item 2 Item 3 ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Reference Currently, Lynx supports the following common flexible box layout properties: - **CSS Properties** - [`flex`](/api/css/properties/flex.md) - [`flex-basis`](/api/css/properties/flex-basis.md) - [`flex-direction`](/api/css/properties/flex-direction.md) - [`flex-flow`](/api/css/properties/flex-flow.md) - [`flex-grow`](/api/css/properties/flex-grow.md) - [`flex-shrink`](/api/css/properties/flex-shrink.md) - [`flex-wrap`](/api/css/properties/flex-wrap.md) - [`order`](/api/css/properties/order.md) - **Alignment Properties** - [`align-content`](/api/css/properties/align-content.md) - [`align-items`](/api/css/properties/align-items.md) - [`align-self`](/api/css/properties/align-self.md) - [`justify-content`](/api/css/properties/justify-content.md) - [`row-gap`](/api/css/properties/row-gap.md) - [`column-gap`](/api/css/properties/column-gap.md) - [`gap`](/api/css/properties/gap.md) For more usage details, please refer to the [CSS Flexible Box Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout) on MDN. [`flex-grow`]: /api/css/properties/flex-grow.md [`flex-shrink`]: /api/css/properties/flex-shrink.md [`flex-wrap`]: /api/css/properties/flex-wrap.md --- url: /guide/ui/layout/grid-layout.md --- # Grid Layout If you want a responsive layout where multiple elements are staggered both vertically and horizontally, the **grid layout** is your best choice. This layout is based on a two-dimensional grid, and it is the most powerful CSS layout on the Web. :::info For further details, please refer to MDN's [css grid layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout). In Lynx, the grid layout largely follows with web standards. Currently, Lynx does not support [`[line-names]`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Grid_layout_using_named_grid_lines) and [`grid-area`](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-area). Please refer to the [Reference](#reference) section for supported properties. ::: **Here is a brief guide on using the grid layout.** ## How to Build a Grid Layout? Before you build a grid layout, it's important first to understand the basic concepts. ### Basic Concepts - **Grid Containers and Grid Items** The parent element using grid layout is called a "grid container." The children within the container that are layouted using grid layout are called "grid items". - **Grid Lines** The lines that divide the grid layout are called "grid lines." Typically, x rows have x+1 horizontal grid lines, y columns have y+1 vertical grid lines, as shown in the image below with 6 horizontal and 4 vertical grid lines. - **Grid Rows and Grid Columns** The spaces between two grid lines are called grid tracks. The horizontal tracks in the container are called "grid rows," and vertical tracks are called "grid columns." - **Grid Cells** The intersection of rows and columns forms a grid cell. - **Grid Areas** The area occupied by one or more grid cells by a grid item is called a grid area. - **Inline Axis and Block Axis** As Lynx does not support [`writing-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode), the inline axis is horizontal, and the block axis is vertical. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/layout/grid_define.png) ### Step 1: Apply `display: grid` To implement the grid layout, set `display: grid` on the parent element. ```css display: grid; ``` ### Step 2: Specify the Size of Rows and Columns Once the container has the grid layout specified, define the rows and columns. The [`grid-template-columns`] property specifies each column's width, while the [`grid-template-rows`] property defines each row's height. If `grid-template-columns` or `grid-template-rows` are not explicitly used to define dimensions, the grid layout uses [`grid-auto-columns`] and [`grid-auto-rows`] for determining column widths and row heights. **This is an example below: layout** **Entry:** `src/grid_size` **Bundle:** `dist/grid_size.lynx.bundle` | Web: `dist/grid_size.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const GridSize = () => { return ( grid-template-columns: 1fr 100px; grid-template-rows: repeat(3, 1fr); ONE TWO THREE FOUR FIVE ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Step 3: Specify Grid Gaps Grid gap is the spacing between grid tracks. It can be indicated by [`column-gap`] for columns, [`row-gap`] for rows, or [`gap`] for both columns and rows. **This is an example below: layout** **Entry:** `src/grid_gap` **Bundle:** `dist/grid_gap.lynx.bundle` | Web: `dist/grid_gap.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const GridGap = () => { return ( gap: 20px; ONE TWO THREE FOUR FIVE column-gap: 20px; ONE TWO THREE FOUR FIVE row-gap: 20px; ONE TWO THREE FOUR FIVE ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Step 4: Align Grid Tracks to Inline and Block Axes Align the grid tracks with the inline (horizontal axis) and block (vertical axis) using [`justify-content`] and [`align-content`]. **This is an example below: layout** **Entry:** `src/grid_axis_alignment` **Bundle:** `dist/grid_axis_alignment.lynx.bundle` | Web: `dist/grid_axis_alignment.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const GridAxisAlignment = () => { return ( justify-content: center; align-content: start; ONE TWO THREE FOUR FIVE justify-content: end; align-content: center; ONE TWO THREE FOUR FIVE ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Step 5: Specify the Grid Lines for Grid Items The positions of grid items can be specified. Use [`grid-column-start`] and [`grid-column-end`] to set the columns a grid area spans, and [`grid-row-start`] and [`grid-row-end`] to define rows. **This is an example below: layout** **Entry:** `src/grid_placement` **Bundle:** `dist/grid_placement.lynx.bundle` | Web: `dist/grid_placement.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const GridAxisAlignment = () => { return ( grid-column-start: span 2 grid-column-start: 2; grid-row-start: 2 grid-column-start: span 2; grid-row-end: -2 grid-column-end: -2; grid-row-end: -1 ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Step 6: Align Grid Items to the Grid Area Having established the grid items' respective grid areas in previous steps, you can now use [`align-items`] and [`align-self`] to vertically align grid items to the grid area, and [`justify-items`] and [`justify-self`] to horizontally align them as well. It's notable that `align-self` and `justify-self` settings on grid items will override those set by `align-items` and `justify-items` on the container. **This is an example below: layout** **Entry:** `src/grid_area_alignment` **Bundle:** `dist/grid_area_alignment.lynx.bundle` | Web: `dist/grid_area_alignment.web.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const GridAxisAlignment = () => { return ( align-items: center; justify-items: center; align-self: end; align-self: stretch; justify-self: stretch; justify-self: end; align-self: start; align-self: stretch; justify-self: stretch; justify-self: start; align-self: stretch; justify-self: stretch; ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Reference Currently, Lynx supports the following common grid layout properties: - **CSS Properties** - [`grid-template-columns`](/api/css/properties/grid-template-columns.md) - [`grid-template-rows`](/api/css/properties/grid-template-rows.md) - [`grid-auto-columns`](/api/css/properties/grid-auto-columns.md) - [`grid-auto-rows`](/api/css/properties/grid-auto-rows.md) - [`grid-auto-flow`](/api/css/properties/grid-auto-flow.md) - [`grid-row-start`](/api/css/properties/grid-row-start.md) - [`grid-row-end`](/api/css/properties/grid-row-end.md) - [`grid-column-start`](/api/css/properties/grid-column-start.md) - [`grid-column-end`](/api/css/properties/grid-column-end.md) - **Alignment Properties** - [`align-content`](/api/css/properties/align-content.md) - [`align-items`](/api/css/properties/align-items.md) - [`align-self`](/api/css/properties/align-self.md) - [`justify-content`](/api/css/properties/justify-content.md) - [`justify-items`](/api/css/properties/justify-items.md) - [`justify-self`](/api/css/properties/justify-self.md) - [`row-gap`](/api/css/properties/row-gap.md) - [`column-gap`](/api/css/properties/column-gap.md) - [`gap`](/api/css/properties/gap.md) For more information on usage, please refer to MDN's [css grid layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout). [`grid-template-columns`]: /api/css/properties/grid-template-columns.md [`grid-template-rows`]: /api/css/properties/grid-template-rows.md [`grid-auto-columns`]: /api/css/properties/grid-auto-columns.md [`gap`]: /api/css/properties/gap.md [`column-gap`]: /api/css/properties/column-gap.md [`row-gap`]: /api/css/properties/row-gap.md [`grid-auto-rows`]: /api/css/properties/grid-auto-rows.md [`grid-column-start`]: /api/css/properties/grid-column-start.md [`grid-column-end`]: /api/css/properties/grid-column-end.md [`grid-row-start`]: /api/css/properties/grid-row-start.md [`grid-row-end`]: /api/css/properties/grid-row-end.md [`justify-content`]: /api/css/properties/justify-content.md [`align-content`]: /api/css/properties/align-content.md [`align-items`]: /api/css/properties/align-items.md [`align-self`]: /api/css/properties/align-self.md [`justify-items`]: /api/css/properties/justify-items.md [`justify-self`]: /api/css/properties/justify-self.md --- url: /guide/ui/layout/relative-layout.md --- # Relative Layout no Web If you want a layout that allows easily control the relative position between the parent and children or between the sibling elements without using complex hierarchical structure, **relative layout** (inspired by [relative Layout](https://developer.android.com/develop/ui/views/layout/relative) in Android) is the best choice. While using [grid](/guide/ui/layout/grid-layout.md), [flexible box](/guide/ui/layout/flexible-box-layout.md), and [linear](/guide/ui/layout/linear-layout.md) layouts, it's challenging to achieve a design that includes numerous relative positions using only a few styles. Relative layout is a layout that displays children in relative positions, where each view's position can be specified relative to sibling elements (for example, to the left or below another view) or relative to the parent's area (e.g., align at bottom, left or center). For the supported properties, please refer to the [Reference section](#reference). ## How to Build a Relative Layout? In the scenario described below, where the "user name" and "description" have a positional relationship, and the "user name" also aligns with the "avatar" on the right. What's more, the "follow", "close", "user" also have corresponding relationships with each other. The dashed lines and arrows in the image below are to indicate their positional relationship. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/relative_demo_1.png) **Let's implement the above diagram using the relative layout in following steps:** ### Step 1: Apply `display: relative` You can apply [`display: relative`](/api/css/properties/display.md) to the parent element where you want the relative layout. ```css display: relative; ``` ### Step 2: Set ID for Children Assign a unique [`relative-id`](/api/css/properties/relative-id.md) (integer, not `0`) for each child in the relative layout. This step is to better identify each element. ```css // avatar relative-id: 1; // user_name relative-id: 2; // user_description relative-id: 3; // user_lv relative-id: 4; // close relative-id: 5; // follow relative-id: 6; ``` ### Step 3: Set Edge Alignment Properties Use these edge alignment properties to specify alignment of the element with its **parent or sibling**'s edge. For instance, [`relative-align-top`](/api/css/properties/relative-align-top.md) ensures the element aligns with the top edge of the designated parent or sibling id. Physical Properties - [`relative-align-top`](/api/css/properties/relative-align-top.md)、[`relative-align-right`](/api/css/properties/relative-align-right.md)、[`relative-align-bottom`](/api/css/properties/relative-align-bottom.md)、[`relative-align-left`](/api/css/properties/relative-align-left.md) Logical Properties - [`relative-align-inline-start`](/api/css/properties/relative-align-inline-start.md)、[`relative-align-inline-end`](/api/css/properties/relative-align-inline-end.md) ### Step 4: Set Relative Position Properties Define relative positioning of the current element to its **sibling** elements using these properties. As an example, [`relative-left-of`](/api/css/properties/relative-left-of.md) arranges the current element to the left of the designated sibling, closely aligning right edge with the sibling's left edge. Physical Properties - [`relative-left-of`](/api/css/properties/relative-left-of.md)、[`relative-right-of`](/api/css/properties/relative-right-of.md)、[`relative-top-of`](/api/css/properties/relative-top-of.md)、[`relative-bottom-of`](/api/css/properties/relative-bottom-of.md) Logical Properties - [`relative-inline-start-of`](/api/css/properties/relative-inline-start-of.md)、[`relative-inline-end-of`](/api/css/properties/relative-inline-end-of.md) ### Step 5: Set Center Property Use [`relative-center`](/api/css/properties/relative-center.md) declare how the current children element is centered in the container. By setting `vertical` to achieve vertical centering, setting `horizontal` to achieve horizontal centering, or setting `both` to simultaneously achieve both vertical and horizontal centering. ### Example ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/layout/lynx_layout_relative_demo.png) In this example, the container's width is fixed, while its height adjusts to its content. To incorporate gaps between elements, use `margin`. **This is an example below: layout** **Entry:** `src/relative_layout` **Bundle:** `dist/relative.lynx.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const RelativeExample = () => { return ( ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` - **Best Practices** Reasonable use of the relative layout can offer developers a convenient and efficient layout experience. Therefore, it is strongly recommended that developers follow the following points when developing. 1. Parent container positioning can be used freely without affecting performance. 2. When using the positioning between sibling elements, it is recommended to enable the [`relative-layout-once`](/api/css/properties/relative-layout-once.md) (default enabled) style, and the sibling element will only rely on upwards. 3. Avoid circular dependencies where 'a' depends on 'b' for its horizontal width, and 'b' depends on 'a' for its vertical width, as this can severely impact performance. 4. Please do not have unresolved circular dependencies, which cannot get the correct layout result. 5. Try to use logical attributes, inline-start and inline-end, to facilitate page internationalization support. ## Reference - **Relative Id** - [`relative-id`](/api/css/properties/relative-id.md) - **Edge Alignment Properties** **Physical Properties** - [`relative-align-top`](/api/css/properties/relative-align-top.md) - [`relative-align-right`](/api/css/properties/relative-align-right.md) - [`relative-align-bottom`](/api/css/properties/relative-align-bottom.md) - [`relative-align-left`](/api/css/properties/relative-align-left.md) **Logical Properties** - [`relative-align-inline-start`](/api/css/properties/relative-align-inline-start.md) - [`relative-align-inline-end`](/api/css/properties/relative-align-inline-end.md) - **Relative Position Properties** **Physical Properties** - [`relative-left-of`](/api/css/properties/relative-left-of.md) - [`relative-right-of`](/api/css/properties/relative-right-of.md) - [`relative-top-of`](/api/css/properties/relative-top-of.md) - [`relative-bottom-of`](/api/css/properties/relative-bottom-of.md) **Logical Properties** - [`relative-inline-start-of`](/api/css/properties/relative-inline-start-of.md) - [`relative-inline-end-of`](/api/css/properties/relative-inline-end-of.md) - **Center Property** - [`relative-center`](/api/css/properties/relative-center.md) - **Layout Optimization Property** - [`relative-layout-once`](/api/css/properties/relative-layout-once.md) --- url: /guide/ui/scrolling.md --- # Managing Scrolling Overflow behavior occurs when the content of an element (its own content and child elements) exceeds the size of the element itself. During the process of building a page, it is inevitable to encounter situations of overflow. You can use the [`overflow`](/api/css/properties/overflow.md) property to crop the overflowing content, or use a \[scrollable element]\(#scrollable element to make the overflowing content scrollable, and control the scrolling direction of the content through the `scroll-orientation` property. non-scrollable element scrollable element | ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/style-guide-overflow-visible.png) | ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/style-guide-overflow-hidden.png) | ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/guide-scroll-orientation-vertical.gif) | ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/guide-scroll-orientation-horizontal.gif) | | Content overflow has occurred. | crop the overflowing content through `overflow: hidden ` | `scroll-orientation: vertical ` scrollable element | `scroll-orientation: horizontal ` scrollable element | Doesn't support`overflow:scroll`for scrolling effect !In `Lynx`, the `view` component doesn't support the scrolling effect achieved by `overflow: scroll` as in the Web. Only scrolling containers like `` and `` have the scrolling effect. ## scrollable element For some basic `` element, the scrolling effect is not supported. Please use dedicated scroll container components [``](/api/elements/built-in/scroll-view.md) or [``](/api/elements/built-in/list.md). ### use `` for basic scrolling `` is a basic scrolling component in `Lynx`. It allows users to scroll content vertically or horizontally within a fixed viewport area. Take the following figure as an example. When the height of the internal child nodes exceeds that of the parent `` container, you only need to set the layout direction `scroll-orientation` to `vertical` to achieve the vertical scrolling effect. **This is an example below: scroll-view** **Entry:** `src/event` **Bundle:** `dist/vertical.lynx.bundle` | Web: `dist/vertical.web.bundle` ```tsx {9} import { root } from "@lynx-js/react"; import { VerticalScrollItem } from "../component/scrollItem.jsx"; const VerticalScrollContainer = () => { return ( ScrollView Example {Array.from({ length: 20 }).map((item, index) => )} ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### use `` to manage large amount of data `` is used to display a small amount of data in a simple and intuitive way. On the other hand, `` is suitable for scenarios where a large amount of data needs to be presented, or in scenarios with infinite scrolling for loading more content. It can adopt an on-demand loading way, rendering only the content in the visible area. **This is an example below: list** **Bundle:** `dist/base.lynx.bundle` | Web: `dist/base.web.bundle` ```tsx {12-14} // Copyright 2024 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import { root } from "@lynx-js/react"; import { ItemView } from "./baseView.jsx"; const ListContainer = () => { return ( {Array.from({ length: 50 }).map((item, index) => { return ( ); })} ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### use `` to handle complex layout `` only has the ability of linear layout, presenting elements in an orderly manner through linear arrangement. However, when facing complex interfaces, `` offers a wide range of layout options. You can choose different layouts such as [flow](/api/elements/built-in/list.md#flow) and [waterfall](/api/elements/built-in/list.md#waterfall) to flexibly customize business requirements. **This is an example below: list** **Entry:** `src/waterfall` **Bundle:** `dist/waterfall.lynx.bundle` | Web: `dist/waterfall.web.bundle` ```tsx {14-16} // Copyright 2024 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import { root } from "@lynx-js/react"; import "./index.scss"; import { ItemView } from "./baseView.jsx"; const ListContainer = () => { return ( {Array.from({ length: 40 }).map((item, index) => { return ( ); })} ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Additional Features - sticky capability:``[sticky](/api/elements/built-in/scroll-view.md#sticky-capability);``[sticky](/api/elements/built-in/list.md#implementing-sticky-nodes) - `` [paginated scrolling](/api/elements/built-in/list.md#implementing-paginated-scrolling) - `` [load more](/api/elements/built-in/list.md#implementing-load-more) --- url: /guide/interaction/event-handling.md --- # Event Handling Lynx provides an event mechanism similar to the Web, allowing developers to design and implement custom interaction logic based on events. However, unlike the Web system, Lynx's event response mechanism supports dual-threaded processing. This means that event handling functions can be executed in the main thread or background thread as needed, thereby optimizing performance and response speed. ## What is an event An event is a signal that triggers an action in the system. When an event is triggered, developers can implement the corresponding logic by listening to the event and executing the corresponding code. For example, developers can listen to click events and modify the background color of a node when a user clicks on the page. **Example 1:** **This is an example below: event** **Entry:** `src/event_bubble` **Bundle:** `dist/event_bubble.lynx.bundle` | Web: `dist/event_bubble.web.bundle` ```tsx {7-12,23} import { root, useState } from "@lynx-js/react"; import type { TouchEvent } from "@lynx-js/types"; export default function App() { const [bgColor, setBgColor] = useState("white"); function handleTap(e: TouchEvent) { const rndCol = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${ Math.floor(Math.random() * 256) })`; setBgColor(rndCol); } return ( click me ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Listen for user clicks When a user clicks on a page, the system triggers a `tap` event. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/event/event_handling.png) As shown in the figure, developers can choose to handle the event in the main thread or the background thread. - When timely event response is not required, you can choose to handle the event in the background thread, and the event processing of the background thread will not block the main thread. - When timely event response is required, you can choose to handle the event in the main thread, which can avoid event delays caused by cross-threading, but it should be noted that excessive event processing may cause the main thread to be busy. Specifically, if developers want to listen to the click event of a certain node, they can set the event handler property of type `bind` on the node: ```tsx ``` If the event handling function runs on the main thread, you need to add an additional `main-thread:` prefix before the event handler property, for example: ```tsx ``` How to intercept or specifically listen to an event?In Lynx , the event handler property can also implement event interception and cross-component event listening. For details, please refer to [Event Propagation](/guide/interaction/event-handling/event-propagation.md). ## Handling user clicks When an event is triggered on a node, the event handling function set by the event handler property will be called. This function will receive an event object as a parameter, which contains detailed information about the event. What are the event objects?All event objects inherit from [Event](/api/lynx-api/event/event.md). Developers can write event processing logic based on event objects in event processing functions. When the event processing function is a [main thread script](/react/main-thread-script.md), you need to add a [`'main thread'`](/api/react/Document.directives.md#main-thread) directive to the first line of the function body to indicate that the function runs on the main thread. ### Main thread event processing In Lynx, the event objects of the main thread and the background thread are different. The event object of the background thread is a pure `json` object, while the event object of the main thread is an operable `Event Object`. How to operate nodes based on event objects?Lynx provides a variety of ways to operate nodes, please refer to [Manipulating elements](/guide/interaction/event-handling/manipulating-element.react.md) for details. For example, for **Example 1**, when the developer chooses to handle events in the main thread, he can directly get [`e.currentTarget`](/api/lynx-api/event/event.md#currenttarget) in the [main thread script](/react/main-thread-script.md) and call [`setStyleProperty`](/api/lynx-api/main-thread/main-thread-element.md#elementsetstyleproperty) to modify the background color of the node. **Example 2** **This is an example below: event** **Entry:** `src/event_node_eom` **Bundle:** `dist/event_node_eom.lynx.bundle` | Web: `dist/event_node_eom.web.bundle` ```tsx {4-11,16} import { root } from "@lynx-js/react"; import type { MainThread } from "@lynx-js/types"; export default function App() { function handleTapInMTS(e: MainThread.TouchEvent) { "main thread"; const rndCol = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${ Math.floor(Math.random() * 256) })`; e.currentTarget.setStyleProperty("background-color", rndCol); } return ( click me ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Background thread event processing For the event processing function of the background thread, developers cannot directly operate the node through [`e.currentTarget`](/api/lynx-api/event/event.md#currenttarget), but can obtain the node reference through [`SelectorQuery`](/api/lynx-api/selector-query.md) and then call [`setNativeProps`](/api/lynx-api/nodes-ref/nodes-ref-set-native-props.md) Modify the background color of the node. **Example 3:** **This is an example below: event** **Entry:** `src/event_node_sq` **Bundle:** `dist/event_node_sq.lynx.bundle` | Web: `dist/event_node_sq.web.bundle` ```tsx {5-16,21} import { root } from "@lynx-js/react"; import type { NodesRef, SelectorQuery, TouchEvent } from "@lynx-js/types"; export default function App() { function handleTap(e: TouchEvent) { const rndCol = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${ Math.floor(Math.random() * 256) })`; (lynx .createSelectorQuery() as { selectUniqueID(uid: number): NodesRef } & SelectorQuery) .selectUniqueID(e.currentTarget.uid) .setNativeProps({ "background-color": rndCol, }) .exec(); } return ( click me ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Summary So far, you have learned how to listen to user clicks and perform corresponding operations based on the event object. For developers, Lynx events provide a Web-like API, but with a unique dual-threaded event response mechanism, allowing developers to choose to perform event processing in the main thread or background thread as needed, thereby optimizing performance and response speed. --- url: /guide/interaction/event-handling/event-propagation.md --- # Event Propagation When an event is triggered, it will propagate along the event response chain. If the corresponding type of event handler property is set on the node, the node can listen to the corresponding event or even intercept it during the event propagation process. In addition, Lynx also provides cross-component event monitoring, event aspect interface, and `GlobalEventEmitter` to implement special event propagation. ## Event handler property By setting the event handler properties, developers can decide at which stage (or across components) of event propagation to listen or intercept the event, and specify the processing function to be called when the event is triggered. The names of these event handler properties are usually composed of the bound event type and event name. | Event Type | Description | | --------------- | ----------------------------------------------------------------------------------- | | `bind` | Listen to events in the bubbling stage, and do not intercept event bubbling. | | `catch` | Listen to events in the bubbling stage and intercept event bubbling. | | `capture-bind` | Listen to events in the capture phase, do not intercept event capture and bubbling. | | `capture-catch` | Listen to events in the capture phase, intercept event capture and bubbling. | | `global-bind` | Listen to events across components. | In particular, when the event handler is a [main thread script](/react/main-thread-script.md), you need to add the `main-thread:` prefix before the event handler property name to ensure that the handler is executed in the main thread. ## Event response chain The event response chain refers to a linked list of nodes that can respond to events. Generally speaking, the event response chain consists of the path from the root node of the page to the node where the action is actually triggered. However, for non-[touch events](/api/lynx-api/event/touch-event.md), the event response chain only contains the node where the action is actually triggered. **Example 1:** **This is an example below: event** **Entry:** `src/event_chain` **Bundle:** `dist/event_chain.lynx.bundle` | Web: `dist/event_chain.web.bundle` ```tsx {50,65,78,95,108} import { root, useState } from "@lynx-js/react"; import type { TouchEvent } from "@lynx-js/types"; export default function App() { const [tap, setTap] = useState(false); const [tap1, setTap1] = useState(false); const [tap11, setTap11] = useState(false); const [tap2, setTap2] = useState(false); const [tap22, setTap22] = useState(false); function handleTap(e: TouchEvent) { if (e.currentTarget.id === "tap") { setTap(!tap); } if (e.currentTarget.id === "tap1") { setTap1(!tap1); } if (e.currentTarget.id === "tap11") { setTap11(!tap11); } if (e.currentTarget.id === "tap2") { setTap2(!tap2); } if (e.currentTarget.id === "tap22") { setTap22(!tap22); } } function handletouchstart(e: TouchEvent) { setTap(false); setTap1(false); setTap11(false); setTap2(false); setTap22(false); } return ( click me 1 click me 2 ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` In the above example, when the user clicks on the page, the background color of the node on the event response chain will be set to orange. ## Event capture The event will go through two stages in the event response chain: event capture and event bubbling. In the event capture stage, the event will start from the root node of the page and propagate down along the event response chain until the node where the action is actually triggered. In the event capture stage, nodes with the event handler property of the `capture-bind` type set can listen to the corresponding event. **Example 2:** **This is an example below: event** **Entry:** `src/event_capture` **Bundle:** `dist/event_capture.lynx.bundle` | Web: `dist/event_capture.web.bundle` ```tsx {7-9,14} import { root, useState } from "@lynx-js/react"; import type { TouchEvent } from "@lynx-js/types"; export default function App() { const [cnt, setCnt] = useState(0); function handleTap(e: TouchEvent) { setCnt(cnt + 1); } return ( Counts the number of clicks on a page: {cnt} {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( item-{item + 1} ); })} ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` In the above example, since event propagation starts from the capture phase, and the capture phase starts from the root node, when the user clicks on the page, the root node can always listen to the `tap` event, thereby realizing the function of counting the number of page clicks. ## Event bubble In the event bubbling phase, the event will propagate upward along the event response chain from the node where the action is actually triggered, until the root node of the page. In the event bubbling phase, nodes with the `bind` type event handler attribute set can listen to the corresponding event. **Example 3** **This is an example below: event** **Entry:** `src/event_bubble` **Bundle:** `dist/event_bubble.lynx.bundle` | Web: `dist/event_bubble.web.bundle` ```tsx {7-12,23} import { root, useState } from "@lynx-js/react"; import type { TouchEvent } from "@lynx-js/types"; export default function App() { const [bgColor, setBgColor] = useState("white"); function handleTap(e: TouchEvent) { const rndCol = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${ Math.floor(Math.random() * 256) })`; setBgColor(rndCol); } return ( click me ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` In the above example, when the user clicks any node on the page, the event will bubble from the child node to the parent node by default. Therefore, the parent node can always listen to the `tap` event and change the background color of the node. ## Event interception During the process of event propagation, the event can be intercepted midway to prevent the event from continuing to propagate. When the `catch` type event handler property is set on the node, the event will be intercepted when it propagates to the node and will no longer propagate. **Example 4** **This is an example below: event** **Entry:** `src/event_static_catch` **Bundle:** `dist/event_static_catch.lynx.bundle` | Web: `dist/event_static_catch.web.bundle` ```tsx {7-12,23,34} import { root, useState } from "@lynx-js/react"; import type { TouchEvent } from "@lynx-js/types"; export default function App() { const [bgColor, setBgColor] = useState("white"); function handleTap(e: TouchEvent) { const rndCol = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${ Math.floor(Math.random() * 256) })`; setBgColor(rndCol); } return ( {}} > click me ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` In the above example, since the `click me` area sets the static interception `tap` event, the event will bubble to the parent node and the background color of the node will change only when the non-`click me` area is clicked. ## Prevent Event Propagation in Main Thread Script When used with [Main Thread Script](/react/main-thread-script.md), event propagation can be prevented by calling the [`stopPropagation`](/api/lynx-api/event/event.md#stoppropagation) method of the event object. **Example 5:** **This is an example below: event** **Entry:** `src/event_stop_propagation` **Bundle:** `dist/event_stop_propagation.lynx.bundle` | Web: `dist/event_stop_propagation.web.bundle` ```tsx {34} import { root, runOnBackground, useState } from "@lynx-js/react"; import "./index.css"; import type { MainThread } from "@lynx-js/types"; export default function App() { const [active, setActive] = useState({ outer: false, middle: false, inner: false }); // Utility to flash a box when it's clicked const flashBT = (key: "outer" | "middle" | "inner") => { setActive((prev) => ({ ...prev, [key]: true })); setTimeout(() => setActive((prev) => ({ ...prev, [key]: false })), 200); }; // Utility to flash a box when it's clicked const flash = ( key: "outer" | "middle" | "inner", ) => { "main thread"; runOnBackground(flashBT)(key); }; function handleOuterTap(e: MainThread.TouchEvent) { "main thread"; flash("outer"); } function handleMiddleTap(e: MainThread.TouchEvent) { "main thread"; flash("middle"); } function handleInnerTap(e: MainThread.TouchEvent) { "main thread"; e.stopPropagation(); // ✋ stop event bubbling flash("inner"); } return ( Outer {active.outer ? "(active)" : "(inactive)"} Middle {active.middle ? "(active)" : "(inactive)"} Inner (click me, stops propagation) {active.inner ? "(active)" : "(inactive)"} ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` In the above example, click the inner will not trigger the `tap` event of the outer. ## Cross-component event listening no Web Generally speaking, when a node is not on the event response chain, the event cannot be monitored. Lynx provides a way to monitor cross-component events, allowing developers to register event monitoring on any node and receive corresponding events. For example, developers can set the event handler property of the `global-bind` type on a node to listen to the `tap` event. When any node is clicked, the node can listen to the `tap` event, thereby realizing the function of counting the number of page clicks. **Example 5:** **This is an example below: event** **Entry:** `src/event_global_bind` **Bundle:** `dist/event_global_bind.lynx.bundle` | Web: `dist/event_global_bind.web.bundle` ```tsx {8-10,12-14,19-20,47-48} import { root, useState } from "@lynx-js/react"; import type { TouchEvent } from "@lynx-js/types"; export function ComponentA() { const [scrollContainer, setScrollContainer] = useState(""); const [cnt, setCnt] = useState(0); function handleScroll(e: TouchEvent) { setScrollContainer(e.target.id); } function handleTap(e: TouchEvent) { setCnt(cnt + 1); } return ( Counts the number of clicks on a page: {cnt} Current scroll container: {scrollContainer} ); } export function ComponentB() { return ( {}} > {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( scroll-1-item-{item + 1} ); })} {}} > {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( scroll-2-item-{item + 1} ); })} ); } export default function App() { return ( ComponentA ComponentB ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` It should be noted that for non-[touch events](/api/lynx-api/event/touch-event.md), the event handler property of the non-cross-component event listening type needs to be set on the monitored node. In addition, developers can also set `global-target` on the node to specify that only events with a specific value of the node [`id`](/api/elements/built-in/view.md#id) are listened (type is `string`, multiple [`id`](/api/elements/built-in/view.md#id) can be specified, separated by commas). ## Event aspect interface Sometimes, developers may need to uniformly listen to and handle events of a specific type somewhere, and do not rely on component registration event listeners. For example, count all triggered `tap` events on the page. At this time, developers can use the event aspect interface ([`beforePublishEvent`](/api/lynx-api/lynx/lynx-before-publish-event.md)) provided by Lynx to implement the corresponding function. **Example 6:** **This is an example below: event** **Entry:** `src/event_aop` **Bundle:** `dist/event_aop.lynx.bundle` | Web: `dist/event_aop.web.bundle` ```tsx {7-12,65} import { root, useMemo, useState } from "@lynx-js/react"; import type { TouchEvent } from "@lynx-js/types"; export default function App() { const [cnt, setCnt] = useState(0); useMemo(() => { "background-only"; lynx.beforePublishEvent.add("tap", () => { setCnt((cnt) => cnt + 1); }); }, []); return ( Counts the number of clicks on a page: {cnt} {[0, 1].map((item) => { return ( {"Don't listen tap event-" + (item + 1)} ); })} {[0, 1, 2, 3].map((item) => { return ( {}} > {"Listen tap event-" + (item + 1)} ); })} ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` The event aspect interface is a type of aspect programming. By injecting the corresponding interface at the event trigger point, the event is forwarded to a certain place when a specific event is triggered. This interface is only implemented in the BTS context. Therefore, it can only be used in the background thread, and the corresponding event can only be listened to when the event processing function is triggered. ## `GlobalEventEmitter` Sometimes, developers may need to pass events between different elements and components, or need to pass events between the client and the front end, and do not rely on the element to register event listeners. At this time, developers can use `GlobalEventEmitter` to achieve global scope transmission of events in a page. Developers can obtain the `GlobalEventEmitter` object through [`lynx.getJSModule`](/api/lynx-api/lynx/lynx-get-js-module.md), which provides the following interfaces: | Function name | Function description | Function parameter | | -------------------- | ------------------------------------------------------------------------------------------- | --------------------------------- | | `addListener` | Subscribe to events and register event listeners. | `(eventName, listener, context?)` | | `removeListener` | Remove the specified listener for a specific event. | `(eventName, listener)` | | `removeAllListeners` | Remove all listeners for a specific event. | `(eventName)` | | `toggle` | Broadcast an event with a specified event name, supporting multiple transparent parameters. | `(eventName, ...data)` | | `trigger` | Broadcasts an event with a specified event name, supporting a transparent parameter. | `(eventName, params)` | Note that `GlobalEventEmitter` is only supported in the BTS context, so it can only be used in background threads. ### Event broadcast Developers can broadcast events through `GlobalEventEmitter` to send events to the front end. In the following example, when the user clicks on the page, the developer broadcasts the event by calling the `toggle` method of `GlobalEventEmitter`, so that the click event is propagated from component `ComponentA` to `ComponentB`. **Example 7:** **This is an example below: event** **Entry:** `src/event_emitter_toggle` **Bundle:** `dist/event_emitter_toggle.lynx.bundle` | Web: `dist/event_emitter_toggle.web.bundle` ```tsx {8-10,22-24,41} import { root, useState } from "@lynx-js/react"; import { useLynxGlobalEventListener } from "@lynx-js/react"; import type { TouchEvent } from "@lynx-js/types"; export function ComponentA() { const [eventLog, setEventlog] = useState(""); useLynxGlobalEventListener("tapitem", (e) => { setEventlog((e as TouchEvent).target.dataset.item); }); return ( Tap on item-{eventLog} ); } export function ComponentB() { function handleTap(e: TouchEvent) { lynx.getJSModule("GlobalEventEmitter").toggle("tapitem", e); } return ( {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( item-{item + 1} ); })} ); } export default function App() { return ( ComponentA ComponentB ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` For the client, the example is as follows: iOSAndroidHarmonyOS```objective-c // You can call the sendGlobalEvent function of LynxContext // The first parameter is the event name monitored by the front end, and the second parameter is the data received by the front end [LynxContext sendGlobalEvent:@"eventName" withParams:args]; // Or call the sendGlobalEvent function of LynxView [LynxView sendGlobalEvent:@"eventName" withParams:args]; ``` ```java // You can call the sendGlobalEvent function of LynxContext // The first parameter is the event name monitored by the front end, and the second parameter is the data received by the front end LynxContext.sendGlobalEvent("eventName", args); // Or call the sendGlobalEvent function of LynxView LynxView.sendGlobalEvent("eventName", args); ``` ```js // You can call the sendGlobalEvent function of LynxContext // The first parameter is the event name monitored by the front end, and the second parameter is the data received by the front end LynxContext.sendGlobalEvent('eventName', args); ``` ## ### Event subscribe Developers can also subscribe to events through the `addListener` method of `GlobalEventEmitter` to receive events from the front end and client. In the following example, users can receive the [`onWindowResize`](/api/lynx-api/event/global-event.md#onwindowresize) event sent by Lynx, which is triggered when the Lynx page size changes. **Example 8:** **This is an example below: event** **Entry:** `src/event_emitter_listen` **Bundle:** `dist/event_emitter_listen.lynx.bundle` | Web: `dist/event_emitter_listen.web.bundle` ```tsx {7-9} import { root, useState } from "@lynx-js/react"; import { useLynxGlobalEventListener } from "@lynx-js/react"; export default function App() { const [eventLog, setEventLog] = useState(""); useLynxGlobalEventListener("onWindowResize", (e) => { setEventLog("" + e); }); return ( Listen onWindowResize Event: LynxView's width has changed to: {eventLog} ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` --- url: /guide/interaction/event-handling/manipulating-element.react.md --- # Direct Manipulation of Elements In daily development, modern front-end frameworks handle most element tree and node property updates for us. However, there are times when you need to manipulate elements directly, such as controlling media players, manipulating view behavior, getting element information, or directly modifying styles. These functionalities are typically implemented by components on the client side, and you need to access them through element references. ## Manipulating Elements in Background Thread ### Example: Auto-scrolling Let's try a simple requirement - auto-scrolling the page. We need to call the [`autoScroll`] method of the `` element: **This is an example below: element-manipulation** **Entry:** `src/ref-background` **Bundle:** `dist/ref-background.lynx.bundle` ```tsx {7,10-18,28} import { useRef } from "@lynx-js/react"; import type { NodesRef } from "@lynx-js/types"; import { ScrollItem } from "../component/scrollItem.jsx"; export const App = () => { const scrollRef = useRef(null); const handleTap = () => { scrollRef.current ?.invoke({ method: "autoScroll", params: { rate: 120, start: true, }, }) .exec(); }; return ( Tap me to enable auto-scroll {Array.from({ length: 20 }).map((item, index) => ( ))} ); }; ``` This example demonstrates two steps for manipulating elements: creating a reference using `useRef` and binding it to the target element using `ref={scrollRef}`, then calling the element method using `invoke()` in the event handler. ### Obtaining a Reference to an Element Using `ref` If you're familiar with React, you'll find that using `ref` is very similar: ```tsx const nodeRef = useRef(null); // ... ; ``` However, note that in Lynx, the type of node reference is [`NodesRef`], which is different from React. If you want to learn more about using references, you can refer to [Manipulating the Element with Refs](https://react.dev/learn/manipulating-the-dom-with-refs). ### Manipulating an Element via Its Reference After obtaining a node reference, let's see how to use it. [`NodesRef`] provides a series of useful APIs. For example, you can use [`NodesRef.invoke()`](/api/lynx-api/nodes-ref/nodes-ref-invoke.md) to call the element's methods. Each component provides specific methods that are implemented on the client side and exposed for front-end use. When calling a method, you can pass required parameters through `params`, handle successful results using the `success` callback, and handle potential errors using the `fail` callback. Remember to call [`exec()`](/api/lynx-api/selector-query/selector-query-exec.md) at the end to submit the operation for actual execution: ```tsx ref .invoke({ method: 'boundingClientRect', params: { relativeTo: 'screen', }, success: (res) => { // Handle successful result const { left, top, width, height } = res; }, fail: (err) => { // Handle potential errors console.error('Failed to get element position:', err); }, }) .exec(); ``` ## Manipulating Elements in Main Thread If you want better performance and more intuitive code, you can consider manipulating elements in the main thread. It offers lower operation latency with faster UI response and more natural API calls. Let's see how to implement the same functionality in the main thread: **This is an example below: element-manipulation** **Entry:** `src/ref-main-thread` **Bundle:** `dist/ref-main-thread.lynx.bundle` ```tsx {7,10-14,19,25} import { useMainThreadRef } from "@lynx-js/react"; import { MainThread } from "@lynx-js/types"; import { ScrollItem } from "../component/scrollItem.jsx"; export const App = () => { const scrollRef = useMainThreadRef(null); const handleTap = () => { "main thread"; scrollRef.current?.invoke("autoScroll", { rate: 120, start: true, }); }; return ( Tap me to enable auto-scroll {Array.from({ length: 20 }).map((item, index) => ( ))} ); }; ``` The main changes here are: node operations need to be written in [main thread functions](/api/react/Document.directives.md#main-thread); using [`useMainThreadRef`] and `main-thread:ref` to get the main thread node reference; the node reference type becomes [`MainThread.Element`], which provides various methods for manipulating nodes; and we used [`MainThread.Element.invoke()`] to call the node's [`autoScroll`] method. ## Obtaining Element References via Selectors In certain scenarios, such as when you need to batch operate on elements or dynamically find elements, using selectors can be particularly useful. ### Background Thread In the background thread, we can use the [`SelectorQuery`] API to find elements. Let's look at an example: **This is an example below: element-manipulation** **Entry:** `src/selector-query-background` **Bundle:** `dist/selector-query-background.lynx.bundle` ```tsx {5-6} import { ScrollItem } from "../component/scrollItem.jsx"; export const App = () => { const handleTap = () => { lynx .createSelectorQuery() .select("scroll-view") .invoke({ method: "autoScroll", params: { rate: 120, start: true, }, }) .exec(); }; return ( Tap me to enable auto-scroll {Array.from({ length: 20 }).map((item, index) => ( ))} ); }; ``` Using selectors is simple: first create a query object using [`lynx.createSelectorQuery()`], then use methods like [`select()`] to find elements. To learn about all supported selectors, you can check our [API documentation](/api/lynx-api/selector-query.md). ### Main Thread When manipulating elements in the main thread, things become even simpler. You can use the browser-like [`lynx.querySelector()`] API: **This is an example below: element-manipulation** **Entry:** `src/selector-query-main-thread` **Bundle:** `dist/selector-query-main-thread.lynx.bundle` ```tsx {6-9} import { ScrollItem } from "../component/scrollItem.jsx"; export const App = () => { const handleTap = () => { "main thread"; lynx.querySelector("scroll-view")?.invoke("autoScroll", { rate: 120, start: true, }); }; return ( Tap me to enable auto-scroll {Array.from({ length: 20 }).map((item, index) => ( ))} ); }; ``` ## Obtaining a Reference to the Event Target Element When handling events, we often need to manipulate the element that triggered the event. ### Main Thread In the main thread, you can get the element reference directly from the event object. Similar to browsers, we provide [`target`] and [`currentTarget`] properties: **This is an example below: element-manipulation** **Entry:** `src/event-main-thread` **Bundle:** `dist/event-main-thread.lynx.bundle` ```tsx {6-9} import type { MainThread } from "@lynx-js/types"; export const App = () => { function handleTap(e: MainThread.TouchEvent) { "main thread"; e.currentTarget.setStyleProperty( "color", "linear-gradient(to right, rgb(255,53,26), rgb(0,235,235))", ); } return ( Tap me to change my color! ); }; ``` Here we used [`MainThread.Element.setStyleProperty()`] to modify styles. ## Using `getElementById` API [`getElementById`] is currently our main API for handling animations and CSS variables. Although this is a traditional interface, it's still the best choice when you need to execute JavaScript animations or dynamically modify CSS variable values. To learn more about usage, you can check [Animation API documentation](/api/lynx-api/lynx/lynx-animate-api.md) and [CSS Variables Operation Guide](/api/css/properties/css-variable.md). We are developing more modern APIs to replace [`getElementById`], stay tuned. [`autoScroll`]: /api/elements/built-in/scroll-view.md#autoscroll [`currentTarget`]: /api/lynx-api/event/event.md#currentTarget [`getElementById`]: /api/lynx-api/lynx/lynx-get-element-by-id.md [`lynx.createSelectorQuery()`]: /api/lynx-api/lynx/lynx-create-selector-query.md [`lynx.querySelector()`]: /api/lynx-api/main-thread/lynx-query-selector.md [`MainThread.Element`]: /api/lynx-api/main-thread/main-thread-element.md [`MainThread.Element.invoke()`]: /api/lynx-api/main-thread/main-thread-element.md#elementinvoke [`MainThread.Element.setStyleProperty()`]: /api/lynx-api/main-thread/main-thread-element.md#elementsetstyleproperty [`NodesRef`]: /api/lynx-api/nodes-ref.md [`select()`]: /api/lynx-api/selector-query/selector-query-select.md [`SelectorQuery`]: /api/lynx-api/selector-query.md [`target`]: /api/lynx-api/event/event.md#target [`useMainThreadRef`]: /api/react/Function.useMainThreadRef.md --- url: /guide/interaction/visibility-detection.md --- # Visibility detection Lynx provides two capabilities for detecting node visibility. One is Lynx's unique exposure capability, which allows developers to easily monitor whether a node is exposed. The other is a Web-like intersection observer, which is a more atomic capability that allows developers to monitor the intersection positions of nodes. ## Detect whether a node is exposed When developers are mainly concerned about whether multiple nodes are on the screen and not the intersection of nodes, and want to write code quickly, they can use [exposure ability](/guide/interaction/visibility-detection/exposure-ability.md). [Exposure ability](/guide/interaction/visibility-detection/exposure-ability.md) is a declarative interface. Developers can specify the nodes that need to monitor exposure through the [`exposure-id`](/api/elements/built-in/view.md#exposure-id) attribute. When the node appears, the exposure event `exposure` is triggered, and when the node disappears, the anti-exposure event `disexposure` is triggered. In the following example, the developer monitors whether the node is exposed/anti-exposed and displays the node [`exposure-id`](/api/elements/built-in/view.md#exposure-id) visible on the screen in real time. **Example 2:** **This is an example below: event** **Entry:** `src/visibility_expose` **Bundle:** `dist/visibility_expose.lynx.bundle` | Web: `dist/visibility_expose.web.bundle` ```tsx {8-12,14-21,62} import { root, useState } from "@lynx-js/react"; import { useLynxGlobalEventListener } from "@lynx-js/react"; export default function App() { const [eventLog, setEventLog] = useState(""); useLynxGlobalEventListener("exposure", (e) => { (e as { "exposure-id": string }[]).forEach((item) => { setEventLog((log) => log + (log === "" ? "" : ", ") + item["exposure-id"]); }); }); useLynxGlobalEventListener("disexposure", (e) => { let log = eventLog.split(", "); (e as { "exposure-id": string }[]).forEach((item) => { log = log.filter(id => id !== item["exposure-id"]); }); log.sort(); setEventLog(log.join(", ")); }); return ( Exposed node: {eventLog} {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( scroll-item-{item + 1} ); })} scroll container ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` Since the exposure capability focuses on whether the node is visible, the node visibility requirement here is more stringent. In addition, since [exposure capability](/guide/interaction/visibility-detection/exposure-ability.md) is a declarative interface, when developers need to monitor the exposure of multiple nodes, they only need to add the [`exposure-id`](/api/elements/built-in/view.md#exposure-id) attribute to the node. ## Detect whether nodes intersect When developers only need to check whether nodes intersect, and do not care whether the nodes are on the screen, they can use the [intersection observer](/guide/interaction/visibility-detection/intersection-observer.md). The [intersection observer](/guide/interaction/visibility-detection/intersection-observer.md) is used to detect whether the target node intersects with the reference node and the target node intersects with the ancestor node. Developers can flexibly specify the reference node, reference node boundary scaling value, node intersection ratio threshold, etc., to achieve a more flexible definition of node visibility. In the following example, the developer monitors whether the parent node and the child node intersect, and outputs the intersecting child node [`id`](/api/elements/built-in/view.md#id) and the intersection position when they intersect. **Example 1:** **This is an example below: event** **Entry:** `src/visibility_intersection` **Bundle:** `dist/visibility_intersection.lynx.bundle` | Web: `dist/visibility_intersection.web.bundle` ```tsx {8-24} import { root, useState } from "@lynx-js/react"; import type { IntersectionObserver, ObserveCallbackResult, TouchEvent } from "@lynx-js/types"; export default function App() { const [eventLog, setEventLog] = useState(""); let observer: IntersectionObserver | null = null; function handleTap(e: TouchEvent) { "background only"; setEventLog(""); if (observer == null) { observer = lynx.createIntersectionObserver({ componentId: "" }, { thresholds: [] }); observer.relativeTo("#container"); for (let i = 1; i <= 7; i++) { observer.observe("#view-item-" + i, (res: ObserveCallbackResult) => { if (res.isIntersecting) { let rect = res.intersectionRect; let rect_str = ", location: [" + rect.left + ", " + rect.top + ", " + rect.right + ", " + rect.bottom + "]"; setEventLog((log) => log + (log === "" ? "node: " : "\nnode: ") + res.observerId + rect_str); } }); } } } return ( { handleTap(e); }} > Click the page to get intersected nodes: {eventLog} {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( view-item-{item + 1} ); })} view container ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` The node visibility here only requires the target node and the reference node to intersect, without requiring the target node to be on the screen, and there is no need to specify the reference node as a scroll container. In addition, since the [intersection observer](/guide/interaction/visibility-detection/intersection-observer.md) is an imperative interface, when developers need to monitor the intersection of multiple nodes, redundant code needs to be written. ## Summary So far, you have learned how to detect whether nodes are intersecting or whether nodes are exposed. For developers, when the focus is on whether a node is on the screen and you want to easily write exposure monitoring code for multiple nodes, you can use [Exposure Ability](/guide/interaction/visibility-detection/exposure-ability.md). when the focus is on whether nodes intersect and where they intersect, or when you need to flexibly define the visibility of nodes, you can use [Intersection Observer](/guide/interaction/visibility-detection/intersection-observer.md). --- url: /guide/interaction/visibility-detection/exposure-ability.md --- # Exposure Ability The exposure capability provides a capability to observe changes in the visibility of a target node. When a target node changes from invisible to visible, an exposure event is triggered. Otherwise, an anti-exposure event is triggered. Developers can monitor the exposure/anti-exposure events of nodes by setting relevant properties for the target nodes to be observed, thereby achieving requirements such as point reporting and `UI` lazy loading. The exposure capability observes changes in node visibility through timed exposure detection tasks. The visibility of a node depends on the following factors: - Visibility of the target node: The target node itself has width and height and is opaque, and the parent node has no clipping with zero width or height. - Viewport intersection of the target node: The target node intersects with the parent scroll container, `Lynxview`, and the viewport of the screen. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/exposure-ability.png) ## Monitor exposure of the entire page When developers need to monitor exposure/anti-exposure events of nodes in the entire page, they can subscribe to the exposure event [`exposure`](/api/lynx-api/event/global-event.md#exposure) and anti-exposure event [`disexposure`](/api/lynx-api/event/global-event.md#disexposure) of the node with the [`exposure-id`](/api/elements/built-in/view.md#exposure-id) attribute set through [`GlobalEventEmitter`](/guide/interaction/event-handling/event-propagation.md#globaleventemitter). In the following example, the developer uses [`GlobalEventEmitter`](/guide/interaction/event-handling/event-propagation.md#globaleventemitter) to monitor whether the node in `ComponentA` is exposed, and outputs the exposed node [`exposure-id`](/api/elements/built-in/view.md#exposure-id) when it is exposed. **Example 1:** **This is an example below: event** **Entry:** `src/visibility_expose_global` **Bundle:** `dist/visibility_expose_global.lynx.bundle` | Web: `dist/visibility_expose_global.web.bundle` ```tsx {8-12,14-21,56} import { root, useState } from "@lynx-js/react"; import { useLynxGlobalEventListener } from "@lynx-js/react"; export function ComponentA() { const [eventLog, setEventLog] = useState(""); useLynxGlobalEventListener("exposure", (e) => { (e as { "exposure-id": string }[]).forEach((item) => { setEventLog((log) => log + (log === "" ? "" : ", ") + item["exposure-id"]); }); }); useLynxGlobalEventListener("disexposure", (e) => { let log = eventLog.split(", "); (e as { "exposure-id": string }[]).forEach((item) => { log = log.filter(id => id !== item["exposure-id"]); }); log.sort(); setEventLog(log.join(", ")); }); return ( Exposed nodes: {eventLog} ); } export function ComponentB() { return ( {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( scroll-item-{item + 1} ); })} ); } export default function App() { return ( ComponentA ComponentB ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` The format of the exposure/anti-exposure event is an array, which contains the target node information of each triggering exposure/anti-exposure event. ```json [ { "exposure-id": string, // exposure-id set on the target node "exposure-scene": string, // exposure-scene set on the target node "sign": string, // uid of the target node "dataset": object, // "data-" field set on the target node //...... }, //...... ] ``` ## Monitor the exposure of a certain node When the developer only needs to listen to the exposure/anti-exposure events of a certain node, you can set the \[event handler]\(../event-handling/event-listening.mdx#Event handler properties) to listen to the node's [`uiappear`](/api/elements/built-in/view.md#binduiappear) and [`uidisappear`](/api/elements/built-in/view.md#binduidisappear) events. In the following example, the developer sets the \[event handler]\(../event-handling/event-listening.mdx#Event handler properties) to listen to whether the node is exposed, and outputs the exposed node [`id`](/api/elements/built-in/view.md#id) when it is exposed. **Example 2:** **This is an example below: event** **Entry:** `src/visibility_expose_custom` **Bundle:** `dist/visibility_expose_custom.lynx.bundle` | Web: `dist/visibility_expose_custom.web.bundle` ```tsx {7-9,11-16,57-59} import { root, useState } from "@lynx-js/react"; import type { Target, UIAppearanceDetailEvent } from "@lynx-js/types"; export default function App() { const [eventLog, setEventLog] = useState(""); function handleUIAppear(e: UIAppearanceDetailEvent) { setEventLog((log) => log + (log === "" ? "" : ", ") + e.detail.dataset.item); } function handleUIDisappear(e: UIAppearanceDetailEvent) { let log = eventLog.split(", "); log = log.filter(item => item !== e.detail.dataset.item); log.sort(); setEventLog(log.join(", ")); } return ( Exposed node: {eventLog} {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( scroll-item-{item + 1} ); })} scroll container ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` The event parameter `e.detail` contains the node information. ```json { "type": string // event name "detail": { "exposure-id": string, // exposure-id set on the target node "exposure-scene": string, // exposure-scene set on the target node "unique-id": string, // uid of the target node "dataset": object, // "data-" field set on the target node //...... }, //...... } ``` ## Control exposure detection Lynx also provides some properties and methods to control the execution of exposure detection tasks. For example, developers can use the following methods to control whether the exposure detection task is started, stopped, and the execution frequency. - [`lynx.stopExposure [BTS]`](/api/lynx-api/lynx/lynx-stop-exposure.md) or [`lynx.stopExposure [MTS]`](/api/lynx-api/main-thread/lynx-stop-exposure.md): Called in the main thread or background thread to stop exposure detection, that is, no longer detect the visibility of the target node, and no exposure/anti-exposure events will be triggered later. - [`lynx.resumeExposure [BTS]`](/api/lynx-api/lynx/lynx-resume-exposure.md) or [`lynx.resumeExposure [MTS]`](/api/lynx-api/main-thread/lynx-resume-exposure.md): Called in the main thread or background thread to start exposure detection, that is, restart the visibility detection of the target node, and then trigger the exposure/anti-exposure events normally. - [`lynx.setObserverFrameRate`](/api/lynx-api/lynx/lynx-set-observer-frame-rate.md): used to set the frequency of exposure detection. In addition, developers can also control the exposure detection logic of the node by setting exposure-related properties on the node, such as [`exposure-screen-margin-*`](/api/elements/built-in/view.md#exposure-screen-margin-), [`exposure-ui-margin-*`](/api/elements/built-in/view.md#exposure-ui-margin-), [`exposure-area`](/api/elements/built-in/view.md#exposure-area), etc. --- url: /guide/interaction/visibility-detection/intersection-observer.md --- # Intersection Observer The intersection observer provides a method to observe the intersection status between the target node and the reference node and between the target node and the ancestor node. When the intersection status changes, the corresponding callback is triggered. Developers can observe the changes in the intersection status between the target node and the reference node through the following three steps: 1. Call [`lynx.createIntersectionObserver`](/api/lynx-api/lynx/lynx-create-intersection-observer.md) to create an [`IntersectionObserver`](/api/lynx-api/intersection-observer.md) object and specify the threshold list of intersection status changes. 2. Call the [`relativeTo`](/api/lynx-api/intersection-observer.md) method of the [`IntersectionObserver`](/api/lynx-api/intersection-observer/intersection-observer-relative-to.md) object to specify the reference node. 3. Call the [`observe`](/api/lynx-api/intersection-observer.md) method of the [`IntersectionObserver`](/api/lynx-api/intersection-observer.md) object to specify the target node and callback. 4. Call the [`disconnect`](/api/lynx-api/intersection-observer.md) method of the [`IntersectionObserver`](/api/lynx-api/intersection-observer/intersection-observer-disconnect.md) object to clear the target node and callback. In the following example, the developer monitors whether the parent node and the child node intersect, and outputs the intersecting child node [`id`](/api/elements/built-in/view.md#id) and the intersection position when they intersect. **This is an example below: event** **Entry:** `src/visibility_intersection` **Bundle:** `dist/visibility_intersection.lynx.bundle` | Web: `dist/visibility_intersection.web.bundle` ```tsx {8-24} import { root, useState } from "@lynx-js/react"; import type { IntersectionObserver, ObserveCallbackResult, TouchEvent } from "@lynx-js/types"; export default function App() { const [eventLog, setEventLog] = useState(""); let observer: IntersectionObserver | null = null; function handleTap(e: TouchEvent) { "background only"; setEventLog(""); if (observer == null) { observer = lynx.createIntersectionObserver({ componentId: "" }, { thresholds: [] }); observer.relativeTo("#container"); for (let i = 1; i <= 7; i++) { observer.observe("#view-item-" + i, (res: ObserveCallbackResult) => { if (res.isIntersecting) { let rect = res.intersectionRect; let rect_str = ", location: [" + rect.left + ", " + rect.top + ", " + rect.right + ", " + rect.bottom + "]"; setEventLog((log) => log + (log === "" ? "node: " : "\nnode: ") + res.observerId + rect_str); } }); } } } return ( { handleTap(e); }} > Click the page to get intersected nodes: {eventLog} {[0, 1, 2, 3, 4, 5, 6].map((item) => { return ( view-item-{item + 1} ); })} view container ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` For the specific syntax of the intersection observer, please refer to [`IntersectionObserver`](/api/lynx-api/intersection-observer.md). --- url: /guide/interaction/networking.md --- # Networking Many mobile apps need to load resources from remote URLs. You might want to make a POST request to a REST API or fetch a large amount of static content from another server. ## Using Fetch :::tip This feature depends on the HTTP service provided by the integrated [Lynx Service](/guide/start/integrate-with-existing-apps.md). ::: Lynx provides a [Fetch API](/api/lynx-api/global/fetch.md) that is compatible with the standard Web API. You can refer to the MDN guide on [Using Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) for more information. This example app fetches and displays user posts from the [JSONPlaceholder API](https://jsonplaceholder.typicode.com/). It initializes with a loading state and triggers a Fetch API request to retrieve posts upon mounting. The fetched data is then displayed in a scrollable list, showing each post's ID and title. A "Loading..." message appears if the request is still in progress. **This is an example below: networking** **Entry:** `src/fetch` **Bundle:** `dist/fetch.lynx.bundle` ```tsx import { root, useEffect, useState } from "@lynx-js/react"; import "./index.scss"; interface Post { userId: number; id: number; title: string; body: string; } const App = () => { const [isFetching, setFetching] = useState(true); const [posts, setPosts] = useState([]); const getPosts = async () => { try { const json = await fetch( "https://jsonplaceholder.typicode.com/posts", ).then((res) => res.json()); setPosts(json); } catch (error) { console.error(error); } finally { setFetching(false); } }; useEffect(() => { getPosts(); }, []); return ( {isFetching ? Loading... : ( posts.map((post) => ( {`${post.id} : ${post.title}`} )) )} ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ### Making Requests To fetch content from an arbitrary `URL`, you can pass the `URL` to `fetch`: ```typescript fetch('https://jsonplaceholder.typicode.com/todos/1'); ``` `fetch` also takes an optional second argument that allows you to customize the HTTP request. You may want to specify additional `headers`, make a `POST` request, or provide a JSON `body`: ```typescript fetch('https://jsonplaceholder.typicode.com/todos/1', { method: 'POST', headers: { 'some-header': 'header', 'Content-Type': 'application/json', }, body: JSON.stringify({ firstParam: 'yourValue', secondParam: 'yourOtherValue', }), }); ``` Take a look at the [Fetch Request](/api/lynx-api/global/fetch.md#request) for the properties supported by Lynx. ### Handling the Response The above examples show how you can make a request. In many cases, you will want to do something with the response. Networking is an inherently asynchronous operation. The `fetch` method returns a `Promise`, which makes it straightforward to write code that works asynchronously. You can use the `async/await` syntax to await the promise's resolution: ```typescript const getDataFromApiAsync = async () => { try { const response = await fetch( 'https://jsonplaceholder.typicode.com/todos/1', ); const json = await response.json(); return json; } catch (error) { console.error(error); } }; ``` Take a look at the [Fetch Response](/api/lynx-api/global/fetch.md#response) for the properties supported by Lynx. Don't forget to catch any errors that `fetch` may throw; otherwise, they will be silently ignored. ## Using Streaming Fetch When transmitting large amounts of data, the server typically uses Streaming HTTP to send data incrementally. In such cases, the client can leverage the streaming capabilities of the `fetch` interface to receive and process data in batches in real time, thereby significantly improving data processing efficiency. ```typescript fetch('https://sse.dev/test').then((response) => { const reader = response.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) { break; } else { const text = TextCodecHelper.decode(value); setData(text); } } }); ``` You can refer to the MDN documentation [Response: body](https://developer.mozilla.org/en-US/docs/Web/API/Response/body) to learn how to handle a streaming body. :::tip This is an experimental feature. To enable the streaming reading capability of the fetch interface, you need to set the specific PageConfig `enableFetchAPIStandardStreaming = true` in the page. ::: ## Using EventSource Lynx provides the [EventSource](/api/lynx-api/global/fetch.md) with the same usage as the Web. You can refer to the MDN guides on [Using server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) for more information. ```typescript const url = 'https://sse.dev/test'; const eventSource = new lynx.EventSource(url2); eventSource.onmessage = (event) => { console.log('Received message:', event.data); }; eventSource.onerror = (error) => { console.error('EventSource failed:', error); }; ``` ## Using TextCodecHelper Due to the lack of `TextEncoder`/`TextDecoder` support in `PrimJS`, Lynx provides `TextCodecHelper` for basic encoding and decoding operations. This class supports `UTF-8` conversions between `string` and `arraybuffer`. Usage: Convert `arraybuffer` to `string`: ```typescript const str = TextCodecHelper.decode(arraybuffer); ``` Convert `string` to `arraybuffer`: ```typescript const arraybuffer = TextCodecHelper.encode(str); ``` ## Using Other Networking Libraries The Fetch API is built into Lynx, which means you can use third-party libraries that rely on it. It is important to note that Lynx's Fetch API has subtle differences compared to the [Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). You can check the [Fetch API Reference - Compatibility](/api/lynx-api/global/fetch.md#compatibility) section to learn more about these differences. As a result, you may need to adapt libraries from the web ecosystem to ensure compatibility. If you encounter any issues with the Lynx Fetch API, you are welcome to submit feature requests or [contribute](https://github.com/lynx-family/lynx/blob/develop/CONTRIBUTING.md) to help Lynx better support the web ecosystem. For front-end framework-specific data fetching solutions, such as using [TanStack Query (React Query)](https://tanstack.com/query/latest) in ReactLynx, you can refer to the [Data Fetching](/react/data-fetching.md) chapter of the ReactLynx guide. --- url: /guide/interaction/ifr.md --- # Instant First-Frame Rendering (IFR) Lynx supports "Instant First-Frame Rendering", which means that your page can display content directly when it is loaded, without a white screen or other intermediate states. Remind you of SSR?This is usually achieved on the Web in a way similar to SSR, but Lynx's innovate dual-thread architecture makes it much easier. Your application code runs in Lynx's [JavaScript runtime](/guide/scripting-runtime/index.md), and will run simultaneously on two threads: the [main thread](/guide/spec.md#main-thread-or-lynx-main-thread) and the [background thread](/guide/spec.md#background-thread-aka-off-main-thread). If the data is ready at the beginning, your application code should be able to render the first screen content directly on the main thread. :::tip No magic "Instant First-Frame Rendering" is not magic, Lynx sometimes cannot achieve "Instant First-Frame Rendering": - When the Bundle of your page cannot be loaded synchronously, Lynx cannot achieve "Instant First-Frame Rendering" (for example, when using asynchronous file I/O, the main reason for your page to have a white screen is the asynchronous file I/O) - When the main content of your page needs to be loaded asynchronously, Lynx cannot achieve "Instant First-Frame Rendering" (for example, your page needs to request network data, the main reason for your page to have a white screen is the asynchronous network request) ::: ## Basic Example In the following example, we simulate a complex rendering through an intensive mathematical calculation (calculating the Fibonacci sequence). Although the rendering takes some time (obviously longer than the interval of frames), Lynx completes the rendering synchronously on the main thread, avoiding the UI intermediate state, and achieves "Instant First-Frame Rendering" without any white screen. **This is an example below: ifr** **Entry:** `src/fib` **Bundle:** `dist/fib.lynx.bundle` | Web: `dist/fib.web.bundle` ```tsx {34} import JSBI from "jsbi"; import "./App.css"; const JSBI_ZERO = /* @__PURE__ */ JSBI.BigInt(0); const JSBI_ONE = /* @__PURE__ */ JSBI.BigInt(1); const JSBI_TWO = /* @__PURE__ */ JSBI.BigInt(2); function fib(n: JSBI): JSBI { if (JSBI.lessThan(n, JSBI_ZERO)) { throw new Error("n must be non-negative"); } if (JSBI.equal(n, JSBI_ZERO)) return JSBI_ZERO; if (JSBI.equal(n, JSBI_ONE)) return JSBI_ONE; let a = JSBI_ZERO, b = JSBI_ONE; for (let i = JSBI_TWO; JSBI.lessThanOrEqual(i, n); i = JSBI.add(i, JSBI_ONE)) { const next = JSBI.add(a, b); a = b; b = next; } return JSBI.BigInt(b); } export function App() { const n = JSBI.BigInt(10000); return ( fib({n.toString()}) is {fib(n).toString()} ); } ``` ## Do IFR with Data from Host Platform Using static or preset data for "Instant First-Frame Rendering" is the simplest way, but it can only be used in scenes such as Showcase or Demo. In actual applications, we usually need to use the data of the host platform for "Instant First-Frame Rendering". Go to [Using Data from Host Platform](/guide/use-data-from-host-platform.md) to learn more. :::info The following code uses `initData.mockData`, which is the data we set in LynxExplorer in advance to simulate the data of the host platform, so as to show you how to use the data of the host platform for "Instant First-Frame Rendering". ::: **This is an example below: ifr** **Entry:** `src/initData` **Bundle:** `dist/init_data.lynx.bundle` | Web: `dist/init_data.web.bundle` ```tsx {17} import { useInitData } from "@lynx-js/react"; import "./App.css"; declare module "@lynx-js/react" { interface InitData { mockData: string; } } export function App() { const initData = useInitData(); return ( Hello World {initData.mockData} ); } ``` ## IFR is one of the advantages of Lynx Your end users may easily notice the difference brought by "Instant First-Frame Rendering", which is one of the advantages of Lynx. :::info Video below is slowed down to 0.3x speed for better observation. ::: Other cross-platform solutions (No IFR)Lynx (IFR) It can be seen that when there is no "Instant First-Frame Rendering", opening the App will present the change process of "Splash Screen → White Screen → Content", while Lynx's "Instant First-Frame Rendering" makes the transition after the splash screen ends more natural and provides a better user experience. --- url: /guide/styling/appearance.md --- # Visual Appearance ## Background and Borders You can do a lot creative things with background and borders. Using [`background-image`](/api/css/properties/background-image.md) to apply network image or gradient effects to the background area of an element, using [`border-radius`](/api/css/properties/border-radius.md) to add a rounded corner, using [`box-shadow`](/api/css/properties/box-shadow.md) to create a shadow effect. In the following example, we add a background with gradient effect, two styles of borders at top and left sides, a black shadow to an element with the top right corner rounded. **This is an example below: css** **Entry:** `src/border_background_shadow` **Bundle:** `dist/border_background_shadow.lynx.bundle` | Web: `dist/border_background_shadow.web.bundle` ```tsx {8-12} import { root } from "@lynx-js/react"; function App() { return ( ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` :::info [`border-image`](https://developer.mozilla.org/en-US/docs/Web/CSS/border-image) and related properties are under development. ::: ## Colors With Lynx CSS, you can apply color values to various properties to create the look you want. ### Properties that can have color #### Text - [`color`](/api/css/properties/color.md): The color to use when drawing the text. - [`-x-handle-color`](): The color of selection handlers (the cursor on the two ends of the selected text) when text is selected. - [`text-shadow`](/api/css/properties/text-shadow.md): The color of the shadow in the shape of text. - [`text-decoration-color`](/api/css/properties/text-decoration.md#text-decoration-color): The color to use when drawing the decoration line on the text. #### Background and Border - [`background-color`](/api/css/properties/background-color.md): The background color of the element. - [`box-shadow`](/api/css/properties/box-shadow.md): The color of shadow. - [`border-color`](/api/css/properties/border-color.md): The color to use when drawing the border. Can be set separately for the foursides via [`border-top-color`](/api/css/properties/border-color.md), [`border-top-color`](/api/css/properties/border-color.md), [`border-top-color`](/api/css/properties/border-color.md) or [`border-top-color`](/api/css/properties/border-color.md) as well. Colors can be set to the property via [`selectors`](/api/css/selectors.md) or the `style` property of the element directly. The color value should be a hex number start with a '#', or a value calculated by function `rgb()`, `rgba()` or `hsl()`. View the specification for [``](/api/css/data-type/color.md) value for more details. ## Gradient You can use [``](/api/css/data-type/gradient.md) value to define a gradient effect and apply it to the following properties: - [`color`](/api/css/properties/color.md): Drawing the text with a gradient effect. - [`background-image`](/api/css/properties/background-image.md): Fill the background area with a gradient effect. - [`mask-image`](/api/css/properties/mask-image.md): Use the gradient effect to create a alpha mask. | ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/color-gradient.png) Text with a gradient `color ` | ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/guide-radial-gradient.png) Filling background with `radial-gradient ` | ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/guide-fading-edge.gif) Create a 'fading edge' effect by adding `linear-gradient ` to `mask-image `property | ## Clipping and Masking In Lynx, besides [`overflow`](/api/css/properties/overflow.md), you can show content of an element in the area you want using [`clip-path`](/api/css/properties/clip-path.md) and [`mask-image`](/api/css/properties/mask-image.md). **This is an example below: css** **Entry:** `src/clip_path_super_ellipse` **Bundle:** `dist/clip_path_super_ellipse.lynx.bundle` | Web: `dist/clip_path_super_ellipse.web.bundle` ```tsx {14} import { root } from "@lynx-js/react"; function App() { return ( LYNX ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` Using clip-path to clip out a super-elliptical area **This is an example below: css** **Entry:** `src/mask_image_circle_gradient` **Bundle:** `dist/mask_image_circle_gradient.lynx.bundle` | Web: `dist/mask_image_circle_gradient.web.bundle` ```tsx {14} import { root } from "@lynx-js/react"; function App() { return ( LYNX ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` Using mask-image to create a circle area with fading edge --- url: /guide/styling/animation.md --- # Motion Lynx offers extensive motion capabilities, allowing developers to create more modern, smooth, and intuitive user interfaces. Utilizing these features, developers can produce stunning transition effects and natural motion feedback, thereby enhancing user experience. ## Add transitions for layout and style changes. If you need to smoothly apply new property values when the layout or style changes, you can use transitions. Transitions provide a way to control the speed of animation changes when altering CSS properties. Instead of having changes take effect immediately, you can have the changes happen gradually over a period of time. For example, when you change an element's color from white to black, normally the change happens instantaneously. With transitions enabled, the change takes place over an interval that follows an easing curve, all of which can be customized. Transitions are automatically triggered, non-repetitive, and easy to use. They can be defined using the shorthand [`transition`](/api/css/properties/transition.md) to set animation properties and duration, or you can specify them individually with [`transition-property`](/api/css/properties/transition-property.md) and [`transition-duration`](/api/css/properties/transition-duration.md), among others. ### Implement Collapse and Expand Effect for List Items You can use transitions to add an animation effect for expanding and collapsing a list item in a list: **This is an example below: animation** **Entry:** `src/transition_toggle` **Bundle:** `dist/toggle_transition_demo.lynx.bundle` | Web: `dist/toggle_transition_demo.web.bundle` ```tsx import { root, useState } from "@lynx-js/react"; import type { ReactNode } from "@lynx-js/react"; import "./index.scss"; const AccordionItem = ({ title, content }: { title: ReactNode; content: ReactNode }) => { const [isActive, setIsActive] = useState(false); const toggleItem = () => { setIsActive(!isActive); }; return ( {title} {content} ); }; const Accordion = () => { const items = [ { title: "Item 1", content: "This is the content of item 1. ".repeat(10), }, { title: "Item 2", content: "This is the content of item 2. ".repeat(10), }, { title: "Item 3", content: "This is the content of item 3. ".repeat(10), }, ]; return ( {items.map((item, index) => )} ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Achieve stunning effects with keyframes. If you need multiple sets of styles to transition in sequence, you can use keyframe animations. Keyframe animations are defined in CSS using the [`@keyframes`](/api/css/at-rule/keyframes.md) rule, which specifies the style changes at various stages of the animation. The [`animation`](/api/css/properties/animation.md) property is then used to apply the defined animation to elements, allowing you to set parameters such as animation name, duration, delay, and number of iterations. Keyframe animations are more flexible and controllable compared to transitions, as they allow you to specify the process and provide finer control over timing curves. You can define them in CSS with [`@keyframes`](/api/css/at-rule/keyframes.md) and specify them using the shorthand [`animation`](/api/css/properties/animation.md) property, or define them with individual properties such as [`animation-name`](/api/css/properties/animation-name.md), [`animation-duration`](/api/css/properties/animation-duration.md), and others. ### To achieve a rotation effect Keyframe animations can be used to achieve the effect of a bounding box rotation. **This is an example below: animation** **Entry:** `src/keyframe_rotate` **Bundle:** `dist/keyframe_rotate.lynx.bundle` | Web: `dist/keyframe_rotate.web.bundle` ```scss page { background-color: #f7f7f7; display: flex; justify-content: center; align-items: center; height: 100vh; } .container { border: 1px solid red; } .title-name-wrapper-border { position: relative; z-index: 0; width: 100%; height: 100%; border-radius: 10px; overflow: hidden; padding: 2rem; } @keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(1turn); } } @keyframes opacityChange { 50% { opacity: 1; } 100% { opacity: 0.5; } } .container { position: relative; z-index: 0; width: 80%; height: 300px; border-radius: 10px; overflow: hidden; } .title-name-wrapper-border-before { position: absolute; z-index: -2; left: -50%; top: -50%; width: 200%; height: 200%; background-color: red; background-repeat: no-repeat, no-repeat, no-repeat, no-repeat; background-size: 50% 50%, 50% 50%; background-position: 0px 0px, 100% 0px, 100% 100%, 0px 100%; background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5); animation: rotate 4s linear infinite; } .title-name-wrapper-border-after { position: absolute; z-index: -1; left: 6px; top: 6px; width: calc(100% - 12px); height: calc(100% - 12px); background: white; border-radius: 5px; animation: opacityChange 3s infinite alternate; } ``` ### To achieve a spring effect Keyframe animations can add spring-like physics to element motion. **This is an example below: animation** **Entry:** `src/keyframe_spring` **Bundle:** `dist/keyframe_spring.lynx.bundle` | Web: `dist/keyframe_spring.web.bundle` ```scss .container { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f5f5f5; } .spring-box { width: 200px; height: 200px; background-color: #4a90e2; border-radius: 8px; display: flex; justify-content: center; align-items: center; } .box-text { color: white; font-size: 24px; } .animate { animation: kf-anim-spring 4s linear forwards; } @keyframes kf-anim-spring { 0% { transform: scale(0, 0); } 0.999% { transform: scale(0.306, 0.306); } 1.998% { transform: scale(0.967, 0.967); } 2.997% { transform: scale(1.586, 1.586); } 3.996% { transform: scale(1.825, 1.825); } 4.995% { transform: scale(1.586, 1.586); } 5.994% { transform: scale(1.046, 1.046); } 6.993% { transform: scale(0.529, 0.529); } 7.992% { transform: scale(0.319, 0.319); } 9.09% { transform: scale(0.54, 0.54); } 10.089% { transform: scale(0.993, 0.993); } 11.088% { transform: scale(1.409, 1.409); } 12.087% { transform: scale(1.561, 1.561); } 13.086% { transform: scale(1.389, 1.389); } 14.085% { transform: scale(1.018, 1.018); } 15.084% { transform: scale(0.671, 0.671); } 16.083% { transform: scale(0.537, 0.537); } 17.082% { transform: scale(0.67, 0.67); } 18.081% { transform: scale(0.973, 0.973); } 19.08% { transform: scale(1.263, 1.263); } 20.079% { transform: scale(1.381, 1.381); } 21.178% { transform: scale(1.258, 1.258); } 22.177% { transform: scale(1.003, 1.003); } 23.176% { transform: scale(0.77, 0.77); } 24.175% { transform: scale(0.685, 0.685); } 25.174% { transform: scale(0.781, 0.781); } 26.173% { transform: scale(0.989, 0.989); } 27.172% { transform: scale(1.184, 1.184); } 28.171% { transform: scale(1.259, 1.259); } 29.17% { transform: scale(1.185, 1.185); } 30.169% { transform: scale(1.015, 1.015); } 31.168% { transform: scale(0.852, 0.852); } 32.167% { transform: scale(0.785, 0.785); } 33.266% { transform: scale(0.855, 0.855); } 34.265% { transform: scale(0.997, 0.997); } 35.264% { transform: scale(1.128, 1.128); } 36.263% { transform: scale(1.176, 1.176); } 38.261% { transform: scale(1.006, 1.006); } 40.259% { transform: scale(0.854, 0.854); } 42.257% { transform: scale(0.991, 0.991); } 44.255% { transform: scale(1.12, 1.12); } 46.353% { transform: scale(1.001, 1.001); } 48.351% { transform: scale(0.9, 0.9); } 52.347% { transform: scale(1.081, 1.081); } 56.343% { transform: scale(0.932, 0.932); } 60.439% { transform: scale(1.055, 1.055); } 64.435% { transform: scale(0.954, 0.954); } 68.431% { transform: scale(1.037, 1.037); } 72.527% { transform: scale(0.968, 0.968); } 76.523% { transform: scale(1.025, 1.025); } 80.519% { transform: scale(0.978, 0.978); } 84.615% { transform: scale(1.017, 1.017); } 88.611% { transform: scale(0.985, 0.985); } 92.607% { transform: scale(1.011, 1.011); } 96.703% { transform: scale(0.99, 0.99); } 100% { transform: scale(1.006, 1.006); } } ``` ## Create flexible keyframe animations in JS. Additionally, our [animate api](/api/lynx-api/lynx/lynx-animate-api.md) extends CSS keyframe animations, allowing for more flexible and dynamic creation and control of animations in JavaScript. Developers can dynamically generate and modify animations at runtime based on interactions or logic, offering users a more vibrant and interactive experience. ### To achieve a variable speed transform motions By using the animate api, we can add a motion to the original that changes its rate in real time. **This is an example below: animation** **Entry:** `src/animate` **Bundle:** `dist/animate.lynx.bundle` ```tsx import { root } from "@lynx-js/react"; import "./index.scss"; const AnimateAnimationExample = () => { return ( { const ani = lynx.getElementById("view1").animate( [ { transform: "translate(0%, 0%) rotate(0deg)", "animation-timing-function": "linear", }, { transform: "translate(200px, 0%) rotate(90deg)", "animation-timing-function": "cubic-bezier(.91,.03,.94,.11)", }, { transform: "translate(200px, 100%) rotate(180deg)", "animation-timing-function": "linear", }, { transform: "translate(0%, 100%) rotate(270deg)", "animation-timing-function": "cubic-bezier(.91,.03,.94,.11)", }, { transform: "translate(0%, 0%) rotate(360deg)", }, ], { name: "js-animation-1", duration: 5000, iterations: Infinity, easing: "linear", }, ); }} > ); }; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` --- url: /guide/styling/custom-theming.md --- # Theming Lynx supports a wide range of [CSS properties](/api/css/properties.md), enabling seamless integration with [CSS selectors](/api/css/selectors.md), [CSS variables](/api/css/properties/css-variable.md), and opt-in CSS inheritance. By defining and managing different theme variables, developers can easily switch between various color schemes, font styles, and other visual elements, ensuring an optimal visual experience and interaction for users. ## Using CSS Descendant Selectors to Switch Themes Similar to web development, using descendant selectors by toggling the class of a parent element can affect the styles of all its descendant nodes, thus enabling the switching of multiple theme styles. ### Defining CSS Themes First, multiple theme CSS styles need to be defined, with different themes having different properties such as colors and backgrounds. For example, we can define both light and dark theme styles, with the light mode defined using `.theme-light .content` and the dark mode defined using `.theme-dark .content`. ```css /* light theme */ .theme-light .content { color: black; background-color: white; } /* dark theme */ .theme-dark .content { color: white; background-color: black; } ``` ### Applying CSS Styles In the page, set the class of the ancestor node (can be defined in the [`page`](/api/elements/built-in/page.md#using-page-element-explicitly)) to `theme-dark` or `theme-light`, and set the class of the descendant nodes to `content`. In this way, the descendant nodes can be styled with `.theme-light .content` or `.theme-dark .content` styles. ```tsx function App() { return ( text ); } ``` ### Switching Theme Styles When the theme changes, switch the class of the ancestor node to `theme-dark` or `theme-light`, which will update the styles of the descendant nodes. In the Lynx development scenario, the front-end themes can be notified by the native client. For example, the native client can notify the front-end of theme changes by updating [globalProps](/api/lynx-api/lynx/lynx-global-props.md). The corresponding front-end implementation: ```tsx import { useMemo } from '@lynx-js/react'; import './App.css'; export function App() { const themeClass = useMemo( () => `theme-${lynx.__globalProps.appTheme}`, [lynx.__globalProps.appTheme], ); return ( //themeClass's value is 'theme-dark' or 'theme-light' ... Hello Theme ... ); } ``` ### Example **This is an example below: css** **Entry:** `src/descendant_selectors_theme` **Bundle:** `dist/descendant_selectors_theme.lynx.bundle` | Web: `dist/descendant_selectors_theme.web.bundle` ```tsx import { root } from "@lynx-js/react"; import { useState } from "react"; import "./index.scss"; export default function App() { const [isDark, setIsDark] = useState(false); const themeClass = isDark ? "theme-dark" : "theme-light"; const toggleTheme = () => { setIsDark(prev => !prev); }; return ( {isDark ? "🌙" : "☀️"} {isDark ? "Dark Mode" : "Light Mode"} Tap to switch theme Card Sample 1 This is a demo card showing theme switching effects. Card Sample 2 This is a demo card showing theme switching effects. Card Sample 3 This is a demo card showing theme switching effects. Card Sample 4 This is a demo card showing theme switching effects. ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Using CSS Variables to Switch Themes When using descendant selectors for theme switching, we need to predefine selectors for different theme styles, which lacks flexibility when dealing with multiple themes. Using [CSS Variable](/api/css/properties/css-variable.md) to define theme styles and achieve theme switching by directly modifying the variable values. ### Defining CSS Themes Similarly, we define the theme style variables that need to change and assign different values to the same variables. For example, under different themes, `color` and `background-color` need to change with the theme. Therefore, two CSS variables `--color` and `--bg` need to be defined. The descendant nodes can obtain the values of these variables in the stylesheet using `var(--color)` and `var(--bg)`. ```css .theme-light { --color: black; --bg: white; } .content { color: var(--color); background-color: var(--bg); } ``` ### Applying CSS Styles Note that CSS variables need to be mounted on the ancestor node (can be defined in the [`page`](/api/elements/built-in/page.md#using-page-element-explicitly)) so that their descendant nodes can use these variables in their respective stylesheets. The descendant nodes can apply the values of these variables in `.content` using the `var(--*)` syntax. ```tsx function App() { return ( text ); } ``` ### Switching Theme Styles #### Directly Changing CSS Variable Values with JS Use JS API ([`setProperty`](/api/css/properties/css-variable.md#modifying-css-variables-via-javascript-api)) to directly modify CSS variable values, allowing flexible batch updates of CSS variables. ```tsx import './App.css'; export function App() { const handleClick = () => { lynx.getElementById('root').setProperty({ '--color': 'white', '--bg': 'black', }); }; return ( Hello Variable ); } ``` #### Indirectly Changing Variable Values by Switching Classes Alternatively, you can indirectly modify variable values by switching classes on the ancestor node that define different [CSS variables](/api/css/properties/css-variable.md#modifying-the-selector-that-declares-css-variables), triggering style updates for all child nodes using these variables when theme switching is needed. For example, use `.theme-light` and `.theme-dark` to define CSS variable values for different themes: ```css .theme-light { --color: black; --bg: white; } .theme-dark { --color: white; --bg: black; } .content { color: var(--color); background-color: var(--bg); } ``` Switching between `.theme-light` or `.theme-dark` on the ancestor node changes the values of `--color` and `--bg`, which updates the styles for corresponding `.content` elements. ```tsx import { useMemo } from '@lynx-js/react'; import './App.css'; export function App() { const themeClass = useMemo( () => `theme-${lynx.__globalProps.appTheme}`, [lynx.__globalProps.appTheme], ); return ( //themeClass's value is 'theme-dark' or 'theme-light' Hello Variable ); } ``` ### Example **This is an example below: css** **Entry:** `src/css_variable_theme` **Bundle:** `dist/css_variable_theme.lynx.bundle` | Web: `dist/css_variable_theme.web.bundle` ```tsx {23-43} import { root } from "@lynx-js/react"; import { useState } from "react"; import "./index.scss"; export default function App() { const [isDark, setIsDark] = useState(false); const [useJS, setUseJS] = useState(false); const [showError, setShowError] = useState(false); const themeClass = isDark ? "theme-dark" : "theme-light"; const toggleTheme = () => { if (useJS) { setShowError(true); setTimeout(() => setShowError(false), 3000); return; } setIsDark(prev => !prev); }; const toggleThemeByJS = () => { setUseJS(true); const rootElement = lynx.getElementById("root"); const newTheme = isDark ? "light" : "dark"; if (rootElement) { if (isDark) { rootElement.setProperty({ "--bg-primary": "#ffffff", "--bg-secondary": "#f5f5f5", "--text-primary": "#1a1a1a", "--text-secondary": "#666666", "--shadow": "rgba(0, 0, 0, 0.1)", }); } else { rootElement.setProperty({ "--bg-primary": "#2d2d2d", "--bg-secondary": "#1a1a1a", "--text-primary": "#ffffff", "--text-secondary": "#cccccc", "--shadow": "rgba(0, 0, 0, 0.3)", }); } setIsDark(prev => !prev); } }; return ( {isDark ? "🌙" : "☀️"} {isDark ? "Dark Mode" : "Light Mode"} Tap to switch theme by className {isDark ? "🌙" : "☀️"} {isDark ? "Dark Mode" : "Light Mode"} Tap to switch theme through JS Card Sample 1 This is a demo card showing theme switching effects. Card Sample 2 This is a demo card showing theme switching effects. Card Sample 3 This is a demo card showing theme switching effects. Card Sample 4 This is a demo card showing theme switching effects. {showError && ( Cannot change variables through the class after modifying them in JS! )} ); } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Leveraging CSS Inheritance As Needed In pages with complex styles, using CSS inheritance can simplify development. However, implementing inheritance logic adds complexity to the style processing flow and can introduce some performance overhead. To optimize performance, Lynx does not enable inheritance for ordinary CSS properties by default, developers must enable it as needed. CSS custom properties (also known as CSS variables) are inherited by descendant nodes by default. ### Inheritance of CSS Custom Properties [CSS Custom Properties](/api/css/properties/css-variable.md) (CSS variables, e.g., `--primary-color`) adhere to Web standards and are inherited by descendant nodes by default without additional configuration. Developers can define CSS custom properties in ancestor nodes to achieve dynamic style management. ### Inheritance of Regular (Non-Custom) Properties To enable inheritance, configure[`enableCSSInheritance`](/api/rspeedy/react-rsbuild-plugin.pluginreactlynxoptions.enablecssinheritance.md). #### Default-Inheritable Properties After enabling `enableCSSInheritance`, these properties can be inherited: [`direction`](/api/css/properties/direction.md),[`color`](/api/css/properties/color.md),[`font-family`](/api/css/properties/font-family.md),[`font-size`](/api/css/properties/font-size.md),[`font-style`](/api/css/properties/font-style.md),[`font-weight`](/api/css/properties/font-weight.md),[`letter-spacing`](/api/css/properties/letter-spacing.md),[`line-height`](/api/css/properties/line-height.md),[`text-align`](/api/css/properties/text-align.md),[`text-decoration`](/api/css/properties/text-decoration.md),[`text-shadow`](/api/css/properties/text-shadow.md) Default inherited properties inherit behavior alignment with [🌐W3C definition](https://www.w3.org/TR/css-cascade-3/#inheriting) #### Custom-Inheritable Properties In addition to the default inheritable properties, you can configure the page with [`customCSSInheritanceList`](/api/rspeedy/react-rsbuild-plugin.pluginreactlynxoptions.customcssinheritancelist.md)to define custom inheritable CSS properties. When there are custom inheritance declarations, only the CSS properties listed in the `customCSSInheritanceList` will be inheritable. Example: ```json "customCSSInheritanceList":["font-size","flex-direction"] ``` #### Limitations of CSS Inheritance 1. Elements with `position: fixed` will always inherit properties only from the page. 2. The keyword "inherit" is not supported. 3. In addition to the default inheritable properties, only CSS properties with types enum or boolean support custom inheritance. --- url: /guide/styling/text-and-typography.md --- # Typography ## Text in Lynx In Lynx, the text content needs to be written inside the [``](/api/elements/built-in/text.md) element. This is different from HTML, where text can be directly written inside a `
`. Let's look at a simple example: ```tsx //❌ This won't work hello world //✅ Use the component hello world ``` You can add styles to the `` element to change the text effect. For example, to change the text color: ```tsx hello world ``` Similarly, to change the text size and make the text italic: ```tsx hello world hello world ``` Lynx also supports adding shadows or strokes to the text by setting the [`text-shadow`](/api/css/properties/text-shadow.md) and [`text-stroke`](/api/css/properties/text-stroke.md) properties to enrich the display effect: **This is an example below: text** **Entry:** `src/shadow_and_stroke` **Bundle:** `dist/shadow_and_stroke.lynx.bundle` | Web: `dist/shadow_and_stroke.web.bundle` ```tsx {15-20} import { root } from "@lynx-js/react"; import { Component } from "@lynx-js/react"; export default class TextShadowAndStrokeExample extends Component { render() { return ( Text Shadow {/* @ts-expect-error TODO(types): Support textStroke in `@lynx-js/types` */} Text Stroke ); } } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Mixing Text with Different Styles In daily text layout, it is often necessary to highlight some parts of the text, such as making keywords bold and changing their color. Suppose we want to make "important word" in the text "This is an important word" bold and red. We can put "important word" into an nested `` and set the `color` and `font-weight` properties. ```tsx This is an important word ``` You can use the properties in the CSS text module to control how text is displayed, such as line-breaking, alignment, and whitespace handling, to achieve more diverse text layout effects. For example, use [`text-indent`](/api/css/properties/text-indent.md) to control the first-line indentation of text, [`word-break`](/api/css/properties/word-break.md) to control the line-breaking behavior of words, and [`text-align`](/api/css/properties/text-align.md) to control the horizontal alignment of text content. The following is an example of the comprehensive use of properties. You can also refer to [text-related properties](/api/elements/built-in/text.md#text-related-css-properties). **This is an example below: text** **Entry:** `src/text_layout` **Bundle:** `dist/text_layout.lynx.bundle` | Web: `dist/text_layout.web.bundle` ```tsx import { root } from "@lynx-js/react"; import { Component } from "@lynx-js/react"; export default class TextLayoutExample extends Component { render() { return ( Text Gradient Title You can {" "} bold {" "} the text that needs to be emphasized. You can add a gradual change to the background color of the text. You can underline the sentence. ); } } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Implementing Text-Image Mixing Layout To create more colorful pages, it is often necessary to embed images in text. The following describes how to mix text and images in layout. Take the following figure as an example: ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/inline-image-demo.png) The first step is to use the `` and `` elements to build the page structure. They cooperate with each other to construct the basic framework. ```tsx This is a warning message.This is a warning message.This is a warning message. ``` The second step is to set the style of the `` element. The key is to set the width and height to ensure that the image is presented appropriately on the page and is compatible with the text. At the same time, set the `text-align` property on the `` element to center the text horizontally. **This is an example below: text** **Entry:** `src/inline_image` **Bundle:** `dist/inline_image.lynx.bundle` | Web: `dist/inline_image.web.bundle` ```tsx {8-18} import ExclamationCircle from "@assets/image/exclamationcircle.png?inline"; import { root } from "@lynx-js/react"; import { Component } from "@lynx-js/react"; export default class Index extends Component { render() { return ( This is a warning message.This is a warning message.This is a warning message. ); } } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` The third step is to adjust the vertical position of the image within the line. By default, the bottom of the `` element aligns with the text baseline. You can use the [`vertical-align`](/api/css/properties/vertical-align.md) property to precisely adjust the vertical position of the `` element within the line. **This is an example below: text** **Entry:** `src/inline_image` **Bundle:** `dist/inline_image.lynx.bundle` | Web: `dist/inline_image.web.bundle` ```tsx {15} import ExclamationCircle from "@assets/image/exclamationcircle.png?inline"; import { root } from "@lynx-js/react"; import { Component } from "@lynx-js/react"; export default class Index extends Component { render() { return ( This is a warning message.This is a warning message.This is a warning message. ); } } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` In addition to images, you can also nest `` within the `` component to create more complex pages. **This is an example below: text** **Entry:** `src/inline_view` **Bundle:** `dist/inline_view.lynx.bundle` | Web: `dist/inline_view.web.bundle` ```tsx {11-26} import { root } from "@lynx-js/react"; import { Component } from "@lynx-js/react"; import "./index.scss"; const InlineView = () => { return ( This is a paragraph containing animation.This is a paragraph containing animation. ); }; export default InlineView; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Text Truncation and Ellipsis When the text content is long and the space is limited, it is necessary to use ellipsis techniques to make the page concise and avoid information clutter. In Lynx, the [`text-overflow`](/api/css/properties/text-overflow.md) property can be used to add an ellipsis effect at the text truncation point. You can choose `ellipsis` to automatically add an ellipsis, or use `clip` to truncate according to the rules. In specific implementation, first limit the number of lines or height of the `` element. When the text exceeds the range, the ellipsis effect will be triggered. Then set the `text-overflow` property to control the presentation method: ```tsx This is an extremely long text. ``` Although `text-overflow` cannot directly specify the content displayed at the truncation point, the `` element provided by Lynx has strong customization capabilities and can display various contents such as images and `` at the truncation point. **This is an example below: text** **Entry:** `src/inline_truncation` **Bundle:** `dist/inline_truncation.lynx.bundle` | Web: `dist/inline_truncation.web.bundle` ```tsx import ExclamationCircle from "@assets/image/exclamationcircle.png?inline"; import RightArrow from "@assets/image/rightarrow.png?inline"; import { root } from "@lynx-js/react"; const InlineTruncation = () => { return ( this is a test text. this is a test text. this is a test text.this is a test text. this is a test text. this is a test text. ...See More this is a test text. this is a test text. this is a test text.this is a test text. this is a test text. this is a test text. ...See More ); }; export default InlineTruncation; root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` ## Custom Font Settings You can directly use [`@font-face`](/api/css/at-rule/font-face.md) to specify custom font resources (the [client needs to support the font resource loader](/api/elements/built-in/text.md#loading-custom-fonts)). At the same time, set the corresponding `font-family` on the `` element. In addition, if you need to load a font file in JS, you can refer to the addFont API designed based on Web Font Loading. This module provides the FontFace class and the [addFont](/api/lynx-api/lynx/lynx-add-font.md) method on the global object `lynx`. **This is an example below: text** **Entry:** `src/custom_font` **Bundle:** `dist/custom_font.lynx.bundle` | Web: `dist/custom_font.web.bundle` ```tsx import { root } from "@lynx-js/react"; import { Component } from "@lynx-js/react"; import fontFile from "../../assets/font/Doto-Regular.ttf"; import "./index.scss"; export default class CustomFontExample extends Component<{}, { fontName: string }> { constructor(props: {}) { super(props); this.state = { fontName: "PingFang" }; } componentDidMount() { lynx.addFont( { "font-family": "Doto", "src": `url(${fontFile})`, }, () => { console.log("load Doto font"); this.setState({ fontName: "Doto" }); }, ); } render() { return ( Whereas recognition of the inherent dignity Whereas recognition of the inherent dignity Whereas recognition of the inherent dignity ); } } root.render(); if (import.meta.webpackHot) { import.meta.webpackHot.accept(); } ``` --- url: /guide/inclusion/accessibility.md --- # Accessibility Accessibility (A11y) refers to the design concept of building accessibility through technical means to ensure that mobile applications can be equally accessed by all kinds of people. Its core goal is to break down usage barriers, allowing users with different physical conditions, perceptual abilities, and cognitive levels to smoothly obtain information and services. Mainstream mobile platforms provide a complete accessibility support system: iOS and Android not only natively integrate APIs for users with disabilities but also come equipped with standardized assistive technology toolchains, such as screen readers (VoiceOver / TalkBack) designed specifically for visually impaired users. On this basis, the Lynx framework encapsulates cross-platform accessibility interfaces, enabling developers to integrate accessibility features in their apps and build an information-accessible mobile ecosystem. :::info Different platforms have distinct designs and norms regarding accessibility; therefore, implementation and experience with Lynx may vary across platforms. ::: ## Default Accessibility Behavior Only one accessibility element can be successfully accessed and focused by screen readers (VoiceOver on iOS and TalkBack on Android). However, `` and `` are default accessibility elements and can be recognized without any further action. **This is an example below: accessibility** **Entry:** `src/Default.tsx` **Bundle:** `dist/main.lynx.bundle` | Web: `dist/main.web.bundle` ```tsx {4-5} export const Default = ({ src }: { src: string }) => { return ( Hello world ); }; ``` ## Tagging Accessibility Elements Sometimes, you might want to control the size of accessibility elements or aggregate some accessibility information, requiring control over which elements are accessibility nodes. Use [`accessibility-element`](/api/elements/built-in/view.md#accessibility-element) to tag an element as an accessibility element; nested elements are allowed. In the example below, there will be only one accessibility focus point and it will be read as "Hello world". **This is an example below: accessibility** **Entry:** `src/Customized.tsx` **Bundle:** `dist/main.lynx.bundle` | Web: `dist/main.web.bundle` ```tsx {5,8,9} export const Customized = ({ src }: { src: string }) => { return ( Hello world ); }; ``` :::info On `` and ``, this property is set to `true` by default. ::: ## Specifying the Characteristics and Content of Accessibility Elements Use [`accessibility-trait`](/api/elements/built-in/view.md#accessibility-trait) to mark the characteristics of an accessibility element, which can be any of image, button, or text. Use [`accessibility-label`](/api/elements/built-in/view.md#accessibility-label) to adjust the content that screen readers will read for an accessibility element. :::info On ``, the default is to read the content of the text. ::: In the example below, the elements will be read as "Hello lynx" and "I am an image displaying the Lynx icon," respectively. **This is an example below: accessibility** **Entry:** `src/LabelAndTraits.tsx` **Bundle:** `dist/main.lynx.bundle` | Web: `dist/main.web.bundle` ```tsx {6,7,16,17} export const LabelAndTraits = ({ src }: { src: string }) => { return ( Hello world ); }; ``` ## Adjusting the Reading Order of Accessibility Elements iOS and Android systems default to arranging accessibility elements from top to bottom and left to right so they can be accessed sequentially by screen readers. Use [`accessibility-elements`](/api/elements/built-in/view.md#accessibility-elements) to manually adjust this order. Note that each id should be separated by a comma. Furthermore, if a parent node sets the [`accessibility-elements`](/api/elements/built-in/view.md#accessibility-elements) property, only child nodes specified by the [`accessibility-elements`](/api/elements/built-in/view.md#accessibility-elements) attribute can be accessed, while other child nodes cannot be focused. In the example below, the order of accessibility elements will be adjusted, and they will be read as "Lynx" and "Hello" sequentially. **This is an example below: accessibility** **Entry:** `src/ReOrder.tsx` **Bundle:** `dist/main.lynx.bundle` | Web: `dist/main.web.bundle` ```tsx {3,5,6} export const ReOrder = ({ src }: { src: string }) => { return ( Hello Lynx ); }; ``` ## Listening to Focus Changes of Accessibility Elements When the focus of accessibility elements changes, a global event `activeElement` notifies the information of the newly focused node. ```tsx export default class App extends Component { componentDidMount() { // Listen for focus changes this.getJSModule('GlobalEventEmitter').addListener( 'activeElement', this.handleActiveElement, this, ); } handleActiveElement(info: any) { this.setState({ activeElementJsonStr: JSON.stringify(info), }); } } ``` ## Actively Focusing on an Accessibility Element Use [`requestAccessibilityFocus`](/api/elements/built-in/view.md#requestaccessibilityfocus) to provide the capability to actively focus on an accessibility element. ```ts lynx .createSelectorQuery() .select('#customId') .invoke({ method: 'requestAccessibilityFocus', params: {}, success: function (res) { console.log(res); }, fail: function (res) { console.log(res); }, }) .exec(); ``` ## Make Screen Readers Announce Specified Text Content Use [`accessibilityAnnounce`](/api/lynx-api/lynx/lynx-accessibility-announce.md#accessibilityAnnounce) to make screen readers announce the specified text content, helping visually impaired users perceive interface state changes or operation results. ```jsx lynx.accessibilityAnnounce( { content: 'hello lynx', }, (res) => { console.log('test: ' + JSON.stringify(res)); }, ); ``` --- url: /guide/inclusion/internationalization.md --- # Internationalization **Internationalization** (i18n) refers to the design and development of products and applications to enable **localization**, making them suitable for users from different cultures, regions, or languages. You can use i18n libraries like `i18next` to achieve internationalization and provide an accessible experience for users. ## Intl API The [`Intl`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl) object is a namespace for the ECMAScript Internationalization API, providing a set of methods for handling internationalization and localization. With the `Intl` API, you can handle issues related to numbers, dates, and times, such as number formatting and date and time formatting. Currently, the `Intl` API is not implemented in Lynx but will be supported in future versions. If you need to use the `Intl` API in Lynx, you can install the corresponding polyfills, such as [@formatjs/intl-numberformat](https://www.npmjs.com/package/@formatjs/intl-numberformat), [@formatjs/intl-datetimeformat](https://www.npmjs.com/package/@formatjs/intl-datetimeformat), and [intl-pluralrules](https://www.npmjs.com/package/intl-pluralrules). ## Using `i18next` [`i18next`](https://www.i18next.com/) is an internationalization-framework written in and for JavaScript. Using it in ReactLynx gives you: 1. **Simplicity**: `i18next` provides an easy-to-use API, making it simple to implement internationalization in ReactLynx applications. 2. **Dynamic Loading**: Supports on-demand loading of language resources, reducing initial load time. 3. **Wide Support**: Compatible with various formats and backends, allowing easy integration with different translation storage solutions such as JSON files, remote APIs, etc. 4. **Caching**: Built-in caching mechanism speeds up the loading of language resources, enhancing user experience. 5. **Rich Community Support**: A vast community and a wealth of plugins available to meet diverse internationalization needs. 6. **Reliability**: Proven in numerous projects, offering stability and reliability. 7. **Hot Reloading**: Changes to language resources can take effect immediately without needing to republish the application. ### Installation You need to install the `i18next` package: ```sh [npm] npm install i18next@^23.16.8 ``` ```sh [yarn] yarn add i18next@^23.16.8 ``` ```sh [pnpm] pnpm add i18next@^23.16.8 ``` ```sh [bun] bun add i18next@^23.16.8 ``` ```sh [deno] deno add npm:i18next@^23.16.8 ``` :::tip Since the version [24.0.0+](https://www.i18next.com/misc/migration-guide#v23.x.x-to-v24.0.0) of i18next, the running environment is required to have the [`Intl.pluralRules`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) API. However, this implementation is currently not available on Lynx. This means that you need to: 1. Use v23 and must enable [`compatibilityJSON: 'v3'`](https://www.i18next.com/misc/json-format#i18next-json-v3). 2. Use v24 and need to [polyfill](https://github.com/eemeli/intl-pluralrules) the `Intl.PluralRules` API. ::: ### Create the first translation Imagine we have a locale file `src/locales/en.json` like this: ```json title="src/locales/en.json" { "world": "World" } ``` Creating the translation function is as simple as these 3 steps: 1. Import the locale JSON file `./locales/en.json`. 2. Create an i18next instance with the [`createInstance()`](https://www.i18next.com/overview/api#createinstance) function. 3. [Initialize](https://www.i18next.com/overview/api#init) the i18n with the locale resource. ```typescript title="src/i18n.ts" import i18next from 'i18next'; import type { i18n } from 'i18next'; import enTranslation from './locales/en.json'; const localI18nInstance: i18n = i18next.createInstance(); localI18nInstance.init({ lng: 'en', // The default JSON format needs `Intl.PluralRules` API, which is currently unavailable in Lynx. compatibilityJSON: 'v3', resources: { en: { translation: enTranslation, // `translation` is the default namespace }, }, }); export { localI18nInstance as i18n }; ``` :::tip If you import `*.json` in TypeScript file, you may need to set `compilerOptions.resolveJsonModule` to `true` in your `tsconfig.json` file. ```json title="tsconfig.json" { "compilerOptions": { "resolveJsonModule": true } } ``` ::: Then, the `i18n.t` function can be used for translations: ```tsx title="src/App.tsx" import { useEffect } from '@lynx-js/react'; import { i18n } from './i18n.js'; // [!code highlight] export function App() { useEffect(() => { console.log(`Hello, ReactLynx x i18next!`); }, []); return ( Hello, {i18n.t('world')} ); } ``` ### Load resources synchronously In a real world project, there are usually multiple resource files for different languages. Instead of static import them one-by-one, you may use the [`import.meta.webpackContext`](https://rspack.rs/api/runtime-api/module-variables#importmetawebpackcontext) API of Rspack to statically import all the JSON files. import one-by-one```js // Static-imported locales that can be shown at first screen import enTranslation from './locales/en.json'; import zhTranslation from './locales/zh.json'; import itTranslation from './locales/it.json'; import jpTranslation from './locales/jp.json'; import deTranslation from './locales/de.json'; import esTranslation from './locales/es.json'; import frTranslation from './locales/fr.json'; import idTranslation from './locales/id.json'; import ptTranslation from './locales/pt.json'; ``` ```js const localesContext = import.meta.webpackContext('./locales', { recursive: false, regExp: /\.json$/, }); const enTranslation = localesContext('en.json'); ``` These resources can be added to `i18next.init()` to make translation work at the first screen. ```typescript title="src/i18n.ts" import i18next from 'i18next'; import type { i18n } from 'i18next'; // Localizations imported statically, available at the initial screen const localesContext = import.meta.webpackContext('./locales', { recursive: false, regExp: /\.json$/, }); const localI18nInstance: i18n = i18next.createInstance(); localI18nInstance.init({ lng: 'en', // The default JSON format needs Intl.PluralRules API, which is currently unavailable in Lynx. compatibilityJSON: 'v3', // Add all statically imported localizations to i18next resources. resources: Object.fromEntries( localesContext.keys().map((key) => [ key.match(/\/([^/]+)\.json$/)?.[1] || key, { translation: localesContext(key) as Record, }, ]), ), }); export { localI18nInstance as i18n }; ``` :::tip You may need [Rspeedy Type Declaration](/rspeedy/typescript.md#rspeedy-type-declaration) for `import.meta.webpackContext`. ::: ### Load resources asynchronously and lazily Instead of bundling all the locales, we can use dynamic imports (`import()`) to load the locales lazily and asynchronously. You need to install the [`i18next-resources-to-backend`](https://github.com/i18next/i18next-resources-to-backend) package: ```sh [npm] npm install i18next-resources-to-backend ``` ```sh [yarn] yarn add i18next-resources-to-backend ``` ```sh [pnpm] pnpm add i18next-resources-to-backend ``` ```sh [bun] bun add i18next-resources-to-backend ``` ```sh [deno] deno add npm:i18next-resources-to-backend ``` Then add the following code to `src/i18n.ts`: ```typescript title="src/i18n.ts" {3,14-23,38} import i18next from 'i18next'; import type { i18n } from 'i18next'; import resourcesToBackend from 'i18next-resources-to-backend'; // Localizations imported statically, available at the initial screen const localesContext = import.meta.webpackContext('./locales', { recursive: false, regExp: /(en|zh)\.json$/, }); const localI18nInstance: i18n = i18next.createInstance(); // We can only loading resources on a background thread if (__JS__) { localI18nInstance.use( // See: https://www.i18next.com/how-to/add-or-load-translations#lazy-load-in-memory-translations resourcesToBackend( (language: string) => // Dynamic-imported locales can be used with `i18n.loadLanguages` import(`./locales/${language}.json`), ), ); } localI18nInstance.init({ lng: 'en', // The default JSON format needs Intl.PluralRules API, which is currently unavailable in Lynx. compatibilityJSON: 'v3', // Add all statically imported localizations to i18next resources. resources: Object.fromEntries( localesContext.keys().map((key) => [ key.match(/\/([^/]+)\.json$/)?.[1] || key, { translation: localesContext(key) as Record, }, ]), ), partialBundledLanguages: true, }); export { localI18nInstance as i18n }; ``` 1. An `i18next` backend `i18next-resources-to-backend` has been added to the background thread with `localI18nInstance.use`. 2. The languages can be loaded asynchronously (with some of them being loaded synchronously). You will see two async JS chunks are created in the output: ```js title=src_locales_it-IT_json.js 'use strict'; exports.ids = ['src_locales_it-IT_json']; exports.modules = { './src/locales/it-IT.json': function (module) { module.exports = JSON.parse('{"world": "Mondo"}'); }, }; ``` ```js title=src_locales_ja-JP_json.js 'use strict'; exports.ids = ['src_locales_ja-JP_json']; exports.modules = { './src/locales/ja-JP.json': function (module) { module.exports = JSON.parse('{"world": "世界"}'); }, }; ``` :::tip 💡 Why is there no async chunk generated by src/locales/en.json This is because this module is already included in the main chunk. Webpack/Rspack will remove it automatically. See: [`optimization.removeAvailableModules`](https://webpack.js.org/configuration/optimization/#optimizationremoveavailablemodules) and [`optimization.removeEmptyChunks`](https://webpack.js.org/configuration/optimization/#optimizationremoveemptychunks) for details. You may also see that these two chunks are not loaded. This is why it is called lazily. The request to the resources is only sent when needed. ::: You may also see that these two chunks are not loaded. This is why it is called lazily. The request to the resources is only sent when needed. ### Change between languages The `i18next.changeLanguage` API can be used for changing between languages. ```jsx title="src/App.tsx" import { useEffect, useState } from '@lynx-js/react'; import { i18n } from './i18n.js'; export function App() { const [locale, setLocale] = useState('en'); useEffect(() => { console.log('Hello, ReactLynx3 x i18next!'); }, []); const getNextLocale = (locale: string) => { // mock locales const locales = ["en", "zh-CN"]; const index = locales.indexOf(locale); return locales[(index + 1) % locales.length]; }; return ( Current locale: {locale} { const nextLocale = getNextLocale(locale); await i18n.changeLanguage(nextLocale); setLocale(nextLocale); }} > Tap to change locale Hello, {i18n.t('world')} ); } ``` **This is an example below: i18n** **Entry:** `src/index.tsx` **Bundle:** `dist/main.lynx.bundle` | Web: `dist/main.web.bundle` ```tsx import { useCallback, useState } from "@lynx-js/react"; import { i18n } from "./i18n.js"; import "./App.css"; export function App() { const [locale, setLocale] = useState("en"); const getNextLocale = (locale: string) => { // mock locales const locales = ["en", "zh-CN"]; const index = locales.indexOf(locale); return locales[(index + 1) % locales.length]; }; return ( Hello, {i18n.t("world")} { const nextLocale = getNextLocale(locale); await i18n.changeLanguage(nextLocale); setLocale(nextLocale); }} className="btn" > Change Language ); } ``` ## Extracting Translations If you downloaded many translations but your project only uses some of them, you may need to extract the translations to reduce the bundle size. There are two ways to extract the translations used in your source code. ### Plugin (Recommended) You can use the [`rsbuild-plugin-i18next-extractor`](https://github.com/rstackjs/rsbuild-plugin-i18next-extractor) to extract the translations used in the source code. This plugin is based on [i18next-cli](https://github.com/i18next/i18next-cli), and uses the Rspack module graph to extract i18n messages from modules that are actually imported by the current build. This allows it to extract translations on demand, avoid bundling unused messages, and reduce the risk of missing translations that are used through imported dependencies. :::tip This plugin requires Node.js 22 or above. ::: First, install the plugin and its `i18next-cli` peer dependency: ```sh [npm] npm install rsbuild-plugin-i18next-extractor i18next-cli -D ``` ```sh [yarn] yarn add rsbuild-plugin-i18next-extractor i18next-cli -D ``` ```sh [pnpm] pnpm add rsbuild-plugin-i18next-extractor i18next-cli -D ``` ```sh [bun] bun add rsbuild-plugin-i18next-extractor i18next-cli -D ``` ```sh [deno] deno add npm:rsbuild-plugin-i18next-extractor npm:i18next-cli -D ``` Enable the plugin in `lynx.config.ts` and specify the `localesDir` option, which is the directory path of the raw translations: The examples below assume your raw translations live in `src/locales/`. ```ts title="lynx.config.ts" import { defineConfig } from '@lynx-js/rspeedy'; import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor'; export default defineConfig({ plugins: [ pluginI18nextExtractor({ localesDir: './src/locales', }), ], }); ``` You should import the translations from the `localesDir` which you specify above: ```ts title="src/i18n.ts" import i18next from 'i18next'; import type { i18n } from 'i18next'; import enTranslation from './locales/en.json'; const localI18nInstance: i18n = i18next.createInstance(); localI18nInstance.init({ lng: 'en', resources: { en: { translation: enTranslation, }, }, }); ``` :::tip After running `DEBUG=rsbuild:i18next rspeedy dev` or `DEBUG=rsbuild:i18next rspeedy build`, you can view the extracted translations in the `node_modules/.rsbuild-plugin-i18next-extractor` directory. ::: #### Configuration Options The plugin scans `node_modules` by default. You can exclude files using the `i18nextToolkitConfig.extract.ignore` option: ```ts title="lynx.config.ts" import { defineConfig } from '@lynx-js/rspeedy'; import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor'; export default defineConfig({ plugins: [ pluginI18nextExtractor({ localesDir: './src/locales', i18nextToolkitConfig: { extract: { ignore: ['node_modules/**'], }, }, }), ], }); ``` You can also provide a callback for missing translation keys: ```ts title="lynx.config.ts" import { defineConfig } from '@lynx-js/rspeedy'; import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor'; export default defineConfig({ plugins: [ pluginI18nextExtractor({ localesDir: './src/locales', onKeyNotFound: (key, locale, localeFilePath, entryName) => { console.warn(`Missing translation key: ${key} for locale: ${locale}`); }, }), ], }); ``` #### Advanced: Dedupe translations in Lynx bundles For Lynx apps, it is recommended to use [`@lynx-js/i18next-translation-dedupe`](https://github.com/lynx-family/lynx-stack/tree/main/packages/i18n/i18next-translation-dedupe) together with `rsbuild-plugin-i18next-extractor` to avoid bundling the same translations twice. `@lynx-js/i18next-translation-dedupe` reads the translations extracted by `rsbuild-plugin-i18next-extractor`, skips the extractor's default rendered asset, and writes the translations into the Lynx bundle `customSections` for runtime loading. First, install the package: ```sh [npm] npm install @lynx-js/i18next-translation-dedupe -D ``` ```sh [yarn] yarn add @lynx-js/i18next-translation-dedupe -D ``` ```sh [pnpm] pnpm add @lynx-js/i18next-translation-dedupe -D ``` ```sh [bun] bun add @lynx-js/i18next-translation-dedupe -D ``` ```sh [deno] deno add npm:@lynx-js/i18next-translation-dedupe -D ``` Then add `pluginLynxI18nextTranslationDedupe()`: ```ts title="lynx.config.ts" import { defineConfig } from '@lynx-js/rspeedy'; import { pluginLynxI18nextTranslationDedupe } from '@lynx-js/i18next-translation-dedupe'; import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor'; export default defineConfig({ plugins: [ pluginI18nextExtractor({ localesDir: './src/locales', }), pluginLynxI18nextTranslationDedupe(), ], }); ``` At runtime, load the extracted translations from the Lynx bundle `customSections`: ```ts title="src/i18n.ts" import i18next from 'i18next'; import type { i18n } from 'i18next'; import { loadI18nextTranslations } from '@lynx-js/i18next-translation-dedupe'; const localI18nInstance: i18n = i18next.createInstance(); localI18nInstance.init({ lng: 'en', resources: loadI18nextTranslations(), }); ``` ### CLI You can also use the [i18next-cli](https://github.com/i18next/i18next-cli) to extract translations. :::tip The i18next-cli requires Node.js 22 or above. ::: First, install the CLI: ```sh [npm] npm install i18next-cli -D ``` ```sh [yarn] yarn add i18next-cli -D ``` ```sh [pnpm] pnpm add i18next-cli -D ``` ```sh [bun] bun add i18next-cli -D ``` ```sh [deno] deno add npm:i18next-cli -D ``` Add an `i18next.config.ts` file at the project root. For example: ```ts title="i18next.config.ts" import { defineConfig } from 'i18next-cli'; export default defineConfig({ locales: ['en', 'zh'], extract: { input: ['src/**/*.{js,jsx,ts,tsx}'], output: 'src/locales/{{language}}.json', }, }); ``` You can adjust `locales`, `extract.input`, and `extract.output` to match your project structure. If you use multiple namespaces, include `{{namespace}}` in the output pattern. Then run the following command to extract translations: ```sh [npm] npm i18next-cli extract ``` ```sh [yarn] yarn i18next-cli extract ``` ```sh [pnpm] pnpm i18next-cli extract ``` ```sh [bun] bun i18next-cli extract ``` ```sh [deno] deno i18next-cli npm:extract ``` --- url: /guide/devtool.md --- # Lynx DevTool Lynx DevTool is a collection of performance and debugging tools for Lynx apps. You need to [integrate DevTool](/guide/start/integrate-lynx-devtool.md) into your Lynx pages, and then connect to the device via the [DevTool Desktop Application](#run-lynx-devtool-desktop-application) to debug the page. ## Experience Lynx DevTool ### Run Lynx Explorer Please visit [Lynx Explorer](/guide/start/quick-start.md) and follow the documentation to run Lynx Explorer app locally. ### Enable Debugging Feature in Lynx Explorer ![Lynx DevTool Switch Page](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-devtool-switch-en-ios.png) Ensure that the Lynx Debug and Lynx DevTool switches are enabled in the Switch page of Lynx Explorer. ### Run Lynx DevTool Desktop Application :::tip Get Lynx DevTool Desktop Application You can visit [Lynx DevTool](https://github.com/lynx-family/lynx-devtool/releases) to get the latest version of Lynx DevTool desktop application. ::: Launch Lynx DevTool Desktop Application. ### Use Data Cable to Connect Debugging Device Use a data cable to connect the debugging device, and Lynx DevTool Desktop Application will automatically recognize the debugging device and display the Lynx app information on the debugging device. ## View Device Connection Status ![View Device Connection Status](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-using-devtool-toolbar-connection.png) In the toolbar, you can view the connection status of the current device. - USB represents that the device is connected via USB cable. - Time represents the delay of communication with the device. ## Choose Debugging Device ![Choose Debugging Device](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-using-devtool-toolbar-choose-device.png) In the toolbar, you can click this button, and select other connected devices from the pop-up menu. ![Choose Debugging Device](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-device-menu.png) :::tip Tip After switching devices, please ensure that the Lynx app on the device is running in the foreground. ::: ## Common Issues and Troubleshooting **Question**: Why can't Lynx DevTool desktop application recognize my debugging device? **Answer**: Make sure you have connected your development device (e.g., your MacBook) and the device running the Lynx application (e.g., your phone) using a data cable. For iOS devices, ensure that you have installed Xcode and iOS SDK with matching versions on your development device. For Android devices, in addition to ensuring a data cable connection, you also need to enable Developer Mode and USB debugging on your Android device: 1. Enable Developer Mode on your Android device - specific steps may vary between devices, please refer to the [Android Developer Documentation](https://developer.android.com/studio/debug/dev-options) 2. Enable "USB Debugging" in the developer options 3. When the device is connected to your computer, an authorization prompt will appear, click "Allow" You can try launching Xcode or Android Studio to compile and run an application to verify that you can connect to the device properly. ## Compatibility **Error:** No compatibility data found for `devtool.integration.connection` ## Provide Feedback Welcome to experience Lynx DevTool. If you need a hand, please file an issue in [Lynx Issues](https://github.com/lynx-family/lynx-devtool/issues). Thank you! --- url: /guide/devtool/panels.md --- # Panels ## Choose Page and Debugging Options ![Choose Page and Debugging Options](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-using-devtool-toolbar-card-settings.png) There may be multiple Lynx page instances at the same time in a page. For debugging between different page instances, you need to select the current page instance to be debugged. ![Debugging Options](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-setting-menu.png) At the bottom of the debugging options page, you can select the page to be debugged. Move the mouse over the page path, and the page thumbnail will be displayed on the right. In the debugging options menu, there are App Info for displaying application and Lynx information, and Settings for setting debugging options. ## Debugging Panels Dive into the debugging functions. ### Elements ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-panel-elements.png) The **Elements** panel allows you to inspect and modify elements. - [View Element Nodes](/guide/devtool/panels/elements-panel.md#view-dom-nodes) - [Edit the Element](/guide/devtool/panels/elements-panel.md#edit-the-dom) - [View and Change CSS](/guide/devtool/panels/elements-panel.md#view-and-change-css) - [Find Invalid, Overridden, and Other CSS](/guide/devtool/panels/elements-panel.md#find-invalid-overridden-and-other-css) ### Console ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-panel-console.png) Use the **Console** panel to view logged messages and run JavaScript. - [View Logged messages](/guide/devtool/panels/console-panel.md#view-logged-messages) - [Run JavaScript](/guide/devtool/panels/console-panel.md#run-javascript) - [Clear the Console](/guide/devtool/panels/console-panel.md#clear-the-console) ### Sources ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-panel-sources.png) Use the **Sources** panel to debug JavaScript. - [File Navigation](/guide/devtool/panels/sources-panel.md#file-navigation) - [Pause Code with Breakpoints](/guide/devtool/panels/sources-panel.md#pause-code-with-breakpoints) - [Debug JavaScript](/guide/devtool/panels/sources-panel.md#debug-javascript) - [Debug Original Code with Source Maps](/guide/devtool/panels/sources-panel.md#debug-original-code-with-source-maps) - [Debug the Main Thread](/guide/devtool/panels/sources-panel.md#debug-the-main-thread) ### Layers ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/doc/debugging-panel-layers.png) Helps you understand the composition of Lynx pages and how the framework presents content, analyzing its 3D layers to discover rendering issues. - [View page Layers](/guide/devtool/panels/layers-panel.md#view-page-layers) - [Inspect page Layers](/guide/devtool/panels/layers-panel.md#inspect-page-layers) --- url: /guide/devtool/panels/elements-panel.md --- .full_image { width: 800px; margin: 20px; } # Elements Panel The **Elements** panel allows you to inspect and modify element and element tree. ## Overview The **Elements** panel provides a powerful interface for inspecting and manipulating the element and the element tree. You can select specific element using the element tree and use other tools to make modifications. The **Elements** panel also includes tabs for the following related tools: - **Styles**: - [View and debug](#viewing-and-changing-css) CSS rules applied to an element from all stylesheets. - Find any [invalid, overridden, or otherwise not working CSS](#invalid-and-declarations-with-invalid-values). - Modify elements by [adding declarations](#adding-css-declarations-to-an-element), and [interacting with the Box model](#interacting-with-elements-in-the-lynx-page-preview-window-via-the-box-model). - **Computed**: Lists the resolved properties applied to an element. ### Open the Elements Panel By default, when you open the Lynx DevTool desktop application and connect to a device to debug Lynx pages, the **Elements** panel will open. To manually open or switch back to the **Elements** panel from another panel, click on the **Elements** tab in the main panel of the DevTool window. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/elements-drawer.png) ### Preview Panel The left side of the **Elements** panel is the Lynx Page Preview Window, showing the Lynx page content for mobile devices in real-time. #### Introduction to the Preview Panel ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/screencast.png) As shown in the above figure: 1. Click the reload icon in the upper left corner to refresh the Lynx page. 2. SD/HD toggle switch, adjust current debugging screen mirroring clarity. 3. Switch current screen mirroring mode, there are two modes: LynxView and FullScreen. - When there are multiple Lynx pages on the current screen, it is recommended to use the LynxView screen mirroring mode, so that you can focus on the currently selected debugging page. - If the Lynx page has an overlay component (this component is in a separate window and may be outside the LynxView display area), it is recommended to use the FullScreen mode, and the entire screen content of the mobile phone will be captured at this time. 4. The border that separates the Elements panel and the preview window can be dragged to adjust their size. 5. Displays the JS engine used by the current page. #### Open/Close the Preview Panel Click on this icon to open/close the preview window. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/screencast-switch.png) After closing, it will be displayed as follows: ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/screencast-close.png) At this time, if an element that can generate UI is selected in the Elements panel (not all nodes on the element tree will eventually generate platform UI), the Lynx page on the mobile phone will highlight the corresponding element. ## View Element Nodes ### Inspect Nodes 1. Click on the **Inspect** icon in the upper left corner of the developer tools. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/elements-inspect.png) 2. Click on the logo in the upper left corner of the preview window. ![Click Inspect](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/elements-click1.png) 3. Now, the corresponding `` node is highlighted in the element tree. ![Click Inspect](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/elements-click2.png) ### Navigate the Element Tree with a keyboard After selecting a node in the element tree, you can use the keyboard to browse the element tree. 1. After clicking on the **Inspect** icon in the upper left corner of the developer tools, click on the input box in the middle of the Lynx page preview window. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/elements-key1.png) Now, the `` node is selected in the element tree. 2. Press the Up arrow key twice. `` has been selected. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/elements-key2.png) 3. Press the left arrow key. All child nodes of `` will be collapsed. 4. Press the left arrow key again, and the parent `` of `` will be selected. 5. Press the Down arrow key twice to reselect the `` node that you just collapsed. The displayed content should be as follows: `...`. 6. Press the right arrow key. At this time, the `` node will be expanded. ## Edit the Element You can dynamically modify the element and see how these changes affect the Lynx page. ### Edit Content To modify the content of a node, double-click on the corresponding content in the element tree. As shown above: 1. After clicking on the **Inspect** icon, click on the text **Lynx Explorer** in the Lynx page preview window. Now the `...` node is selected in the element tree. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/text-modify1.png) 2. Press the right arrow key on the keyboard to expand the `...` node. 3. Press the down arrow key on the keyboard, and the `` node is selected in the element tree. 4. Double-click on the **Lynx Explorer** on ``. 5. Modify the content to **Hello world**. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/text-modify2.png) 6. Press the Enter key, and you can see that the text change has taken effect both in the element tree and on the left preview window. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/text-modify3.png) ### Edit Attributes To modify attributes, double-click on the attribute name or value. Follow the instructions below to learn how to modify the style attribute of a node. 1. Select the `` node in the element tree that you want to modify. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/style-modify1.png) 2. Double-click on the part that displays `style="height:100px;"`, and the text will be highlighted. 3. Modify the **style** content to `style="width:70%;height:100px;"`. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/style-modify2.png) 4. Press the Enter key, and you can see that the width change of the view has taken effect both in the element tree and on the left preview window. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/style-modify3.png) ## View and Change CSS ### View CSS for an Element 1. Click on the **Inspect** icon, and click on the text **Lynx Explorer** in the preview window. 2. The `...` node is selected in the element tree. 3. At this time, you can see that the **Styles** tab shows all the styles applied to the currently selected node. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/css-style-check.png) 4. Switch to the **Computed** tab in the upper tab bar, and you can see the box model of the selected node, as well as all the resolved styles finally applied to the node. ### Add CSS Declarations to an Element If you want to change or add CSS declarations to an element, use the **Styles** tab. 1. Selected `...` in the element tree. 2. On the right side of the **Styles** tab, click on the top `element.style`. 3. After clicking, enter the property name `border` and press the Enter key. A second input box will pop up, enter `black 20px`. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/css-style-modify1.png) 4. Press the Enter key again. At this time, whether it is the UI effect on the preview window or the element tree, the style change has taken effect. {' '} ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/css-style-modify2.png) ### Interact with Elements in the Lynx Page preview window via the Box Model 1. Click on the **Inspect** icon, and hover over the Go button in the preview window. 2. At this time, you can see that all the box models are highlighted in the left preview window, from the inside to the outside are **content-box**/ **padding-box** / **border-box** / **margin-box**. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/box-model.png) 3. Click Go button, then corresponding `...` node is selected in the element tree. 4. Click on the box model in the **Styles** tab on the right side, and click one by one from the inside to the outside, you can see that the left preview window is highlighted one by one in turn. Using content-box as an example, as shown in the image below: ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/box-model2.png) ## Find Invalid, Overridden, and Other CSS ### Check the CSS You Wrote Suppose you added some CSS to an element and want to make sure the new styles are applied correctly. When you refresh the page, the element looks the same as before. Something went wrong. The first thing to do is to inspect the element and make sure the new CSS is actually applied to the element. Sometimes, you will see the new CSS in the **Elements** > **Styles** pane, but the new CSS appears as faded text, cannot be modified, is crossed out, or a warning or hint icon is displayed next to it. ### Understand CSS in the **Styles** Pane The Styles pane can identify various CSS issues and highlight them in different ways. #### Invalid and Declarations with Invalid Values The **Styles** pane will cross out the following and display a warning icon next to the following: - When the CSS property is invalid or unknown, the entire CSS declaration (property and value). - When the CSS property is valid but the value is invalid, the entire CSS declaration (property and value). ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/css-check1.png) #### Overridden The **Styles** pane will cross out properties that are overridden by other properties according to the cascade order. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/css-check2.png) In this example, the `color:red;` style property on the element will override the `color:linear-gradient(120deg,#0095ff 30%,#42d392 100%);` on the `.banner.title` class. #### Inherited and Non-Inherited The **Styles** pane will list properties in the `Inherited from ` section based on the default inheritance relationship of the property: - Inherited content will be shown in regular text by default. - Non-inherited content will be shown in light text by default. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/elements/css-check3.png) ## Compatibility ### View and Edit **Error:** No compatibility data found for `devtool.panels.elements.view-and-edit` ### Preview **Error:** No compatibility data found for `devtool.panels.elements.preview` --- url: /guide/devtool/panels/console-panel.md --- .inline-content { display: flex; align-items: center; flex-wrap: wrap; } .inline-content img { margin: 0 5px; height: 2rem; } .margin { margin-top: 1rem; margin-bottom: 1rem; } .margin2 { margin-top: 0.5rem; } # Console Panel ## Overview Use the **Console** panel to [view logged messages](#view-logged-messages) and [run JavaScript](#run-javascript). Before you start debugging, please take some time to familiarize yourself with the [Lynx JavaScript Runtime](/guide/scripting-runtime/index.md#javascript-runtime). ![Console panel](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/console-panel.png) ### Open the Console Panel The Console can be opened as a panel or as a tab in the Drawer. #### Open in the Drawer If you want to view the Console panel while using other panels, you can open the Console in the Drawer. Please refer to [Open the Console in the Drawer | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#drawer). ### Console Settings Click **Console Settings** ![Console settings](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/console-settings.png) in the top-right corner of the **Console** panel. ![Console settings pane](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/console-settings-pane.png) The following links explain each setting: - [Selected context only](#filter-messages-from-different-contexts) - [Group similar messages in console](#disable-message-grouping) - [Eager evaluation](#disable-eager-evaluation) - [Autocomplete from history](#disable-autocomplete-from-history) ### Console Sidebar Please refer to [Open the Console Sidebar | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#sidebar). ## View Logged messages The types of logs currently can be viewed include: 1. **JavaScript logs**. Including the main thread and the background thread. - By default, logs from the background thread are displayed in full format, while logs from the main thread are serialized as strings with the `[main-thread.js]` prefix. - When [Main Thread Debugging](/guide/devtool/panels/sources-panel.md#debug-the-main-thread) is enabled, logs from the main thread will also be displayed in full format. 2. **Some client logs**. Currently, client runtime errors and some other client logs are serialized as strings and displayed in the Console panel. ![Log type](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/log-type.png) ### Log Sources For JavaScript logs, the `App.tsx:11` on the right side of the log represents where it logged. Clicking it will open the [Sources](/guide/devtool/panels/sources-panel.md) panel and highlight the line of code that caused the message to get logged to the Console. ![Call stack](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/call-stack.png) ### Disable Message Grouping DevTool enables **Group similar messages in console** by default, which aggregates similar messages logged consecutively. ![Group similar log](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/group-similar-log.png) Open [Console Settings](#console-settings) and disable this option to expand the logs that were originally grouped. ![Disable group similar log](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/disable-group-similar-log.png) ### View Stack Traces For JavaScript errors and warnings, click **Expand** ![Stack trace expand](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/stack-trace-expand.png) to view the stack trace. ![Stack trace](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/stack-trace.png) ### Filter messages #### Filter by Log Level Please refer to [Filter by log level | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#level). :::tip When the sidebar is open, you cannot click the log level drop-down. ::: #### Filter by Text Please refer to [Filter by text | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/log#text). #### Filter by Regular Expressions Please refer to [Filter by regular expression | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/log#regex). #### Filter by URL Please refer to [Filter messages by URL | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#url). #### Filter Messages from Different [Contexts](/guide/spec.md#scripting-runtime-enviroment①) By default, all logs are displayed within the context of the background thread. When [Main thread debugging](/guide/devtool/panels/sources-panel.md#debug-the-main-thread) is enabled, an additional context of the main thread will be added. As shown in the figure, `Background:-1` represents the background thread, and `Main` represents the main thread. ![Filter by context](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/filter-by-context.png) When you [run JavaScript in the Console](#run-javascript), it will execute only within the currently selected context. Open [Console Settings](#console-settings) and enable **Selected context only** checkbox to display logs only from the currently selected context. ![Filter by context](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/selected-context-only.png) ### Search for Text Please refer to [Search for text in logs | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#search). ## Run JavaScript The Console is a **REPL**, which stands for "Read, Evaluate, Print, and Loop." It reads the JavaScript you enter, evaluates your code, outputs the result of the expression, and then loops back to the first step. You can enter expressions related to the current page in the Console, such as `this`. You can also enter expressions unrelated to the current page, such as `1+2`. Press Enter to get the result, and the Console will output the result of the expression below the code. ![Run script](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/run-script.png) ### String Copy Options Please refer to [String copy options | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#string-copy-options). ![Copy string](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/console/copy-string.png) For example, in this case, the results of copying are as follows: ```javascript {"a":123,"b":"string"} '{"a":123,"b":"string"}' "{\"a\":123,\"b\":\"string\"}" ``` ### Re-run Expressions from History Please refer to [Re-run expressions from history | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#history). ### Watch Expression Values in Real-Time Please refer to [Watch JavaScript values in real time with Live Expressions | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/live-expressions). ### Disable Eager Evaluation Please refer to [Disable Eager Evaluation | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#eagereval). ### Disable Autocomplete from History Please refer to [Disable autocomplete from history | Chrome DevTools](https://developer.chrome.com/docs/devtools/console/reference#autocomplete). ## Clear the Console You can use any of the following workflows to clear the Console: -
Click **Clear Console** Clear console button .
- Right-click a log and select **Clear Console**. - When the Console is in focus, press Control+L or Command+K. Clear console ## Compatibility **Error:** No compatibility data found for `devtool.panels.console` --- url: /guide/devtool/panels/sources-panel.md --- .inline-content { display: flex; align-items: center; flex-wrap: wrap; } .inline-content img { margin: 0 5px; height: 2rem; } .margin { margin-top: 1rem; margin-bottom: 1rem; } .margin2 { margin-top: 0.5rem; } # Sources Panel Use the **Sources** panel to debug JavaScript. Before you start debugging, please take some time to familiarize yourself with the [Lynx JavaScript Runtime](/guide/scripting-runtime/index.md#javascript-runtime). After DevTool is enabled, the background thread uses the PrimJS engine for debugging by default. On Android, you can also switch to the V8 engine for a more comprehensive debugging experience. To switch to the V8 engine, open the [DevTool Switch Page](/guide/start/integrate-lynx-devtool-advanced.md#debugging-devtool-switch), toggle the "**V8 Engine**" switch to "**On**" and restart the application. You can check the current engine type in the lower-left corner of the [Preview Window](/guide/devtool/panels/elements-panel.md#introduction-to-the-preview-panel). ## Overview The **Sources** panel has three sections: 1. **File Navigation Pane**. All JavaScript files of the page are listed here. 2. **Code Editor Pane**. After selecting a file in the **File Navigation Pane**, the contents of the file are displayed here. 3. **Debugger Pane**. Various tools for inspecting the page's JavaScript. ![Sources panel](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/sources-panel.png) ## File Navigation ### Open File You can use the **File Navigation Pane** or the **Open file** feature to open the file of interest. Click **More Options** ![More options](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/more-options.png)and select **Open file** . A dialog will display. ![Open file](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/open-file.png) You can enter the file URL here or select a file from the drop-down list. ![Open file dialog](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/open-file-dialog.png) The bottom status bar of the **Code Editor Pane** will display the line and column number of the current mouse position. Click **Show navigator** ![Watch Add](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/show-navigator.png)button to collapse/expand the **File Navigation Pane** . ![File editor](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/file-editor.png) ### Close File You can close a file in the following ways: - Hover the mouse over the file name tab at the top of the **Code Editor Pane** and click the **close** button. Close file button - Right-click the file name tab at the top of the **Code Editor Pane**: - Close: Close the current file. - Close others: Close other files. - Close all: Close all files. Close file ### Locate File To locate the file currently displayed in the **Code Editor Pane**: 1. Right-click anywhere in the **Code Editor Pane** or right-click the file name tab at the top of the **Code Editor Pane**. 2. Select **Reveal in sidebar**. ![Reveal in sidebar](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/reveal-in-sidebar.png) ### Search Code To search a code segment: 1. Click **Customize And Control DevTools** > **Search** to open the **Search** panel.
2. Enter text and press Enter to search. 3. Click search results to jump to the corresponding file and the code will be highlighted.
You can also: - Click **Match Case** to make the query case-sensitive. - Click **Use Regular Expression** to search using a RegEx. - Click **Refresh** to search again. - Click **Clear** to clear the input contents. ## Pause Code with Breakpoints By adding breakpoints, you can pause the code execution and inspect all relevant values at that moment. Currently supported breakpoint types are as follows: | Type | Description | | :---------------------------------------------------------------: | ----------------------------------------------------------------------------- | | [Line-of-code](#line-of-code-breakpoints) | Pause on an exact region of code. | | [Conditional line-of-code](#conditional-line-of-code-breakpoints) | Pause on an exact region of code, but only when some other condition is true. | | [Logpoint](#log-line-of-code-breakpoints) | Log a message to the Console without pausing the execution. | | [First line](#first-line-breakpoints) | Pause on the first line of every executed JavaScript file. | | [Exception](#exception-breakpoints) | Pause on the line of code that is throwing a caught or uncaught exception. | ### Line-of-Code Breakpoints Please refer to [Line-of-code breakpoints | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/breakpoints#loc). #### Line-of-Code Breakpoints in Code Please refer to [Line-of-code breakpoints in your code | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/breakpoints#debugger). #### Conditional Line-of-Code Breakpoints Please refer to [Conditional line-of-code breakpoints | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/breakpoints#conditional-loc). #### Log Line-of-Code breakpoints Please refer to [Log line-of-code breakpoints | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/breakpoints#log-loc). #### Manage Line-of-Code Breakpoints Right-click the breakpoint icon or use the **Breakpoints** pane to manage line-of-code breakpoints. - Right-click the breakpoint icon and select **Edit breakpoint** to edit it. You can also change its type from the drop-down list in the inline editor. Edit breakpoint - Click the breakpoint icon again to delete the breakpoint. - In the **Breakpoints** pane, click the checkbox next to the breakpoint entry to enable or disable the breakpoint. When disabled, the marker next to the line number will become transparent. Disable breakpoint - Right-click the breakpoint icon to see the options menu: - Remove breakpoint. - Edit breakpoint. - Disable/Enable breakpoint. Breakpoint action - Right-click the breakpoint entry in the **Breakpoints** pane to see the options menu: - Remove breakpoint. - Reveal location. - Deactivate/Activate breakpoints.
You can also use **Deactivate breakpoints** Step out button to deactivate breakpoints.
When breakpoints are deactivated, DevTool will ignore all line-of-code breakpoints, and they will not be triggered. All breakpoints will remain in the same state after being reactivated. - Disable/Enable all breakpoints. - Disable/Enable breakpoints in file. - Remove all breakpoints. - Remove other breakpoints. Breakpoint pane action #### Skip Breakpoints with 'Never Pause Here' Background V8 Only Please refer to [Skip breakpoints with 'Never pause here' | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/breakpoints#never-pause-here). ### First-Line Breakpoints Background Only Use first-line breakpoints to pause at the entry of every executed JavaScript file. ![First line breakpoint example](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/first-line-bp-example.png) Hover your mouse over the position shown in the image, then enable **First-Line Breakpoints**. This is a non-persistent global switch that takes effect for all pages during a single APP run. The switch state will be reset after closing and restarting the APP. ![First line breakpoint](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/first-line-bp.png) You can debug first-line breakpoints in two ways: 1. Turn on the switch, then open the page you want to debug. 2. Open the page you want to debug, turn on the switch, and then [reload](/guide/devtool/panels/elements-panel.md#introduction-to-the-preview-panel) the page. ### Exception Breakpoints Use exception breakpoints when you want to pause on the line of code that's throwing a caught or uncaught exception. 1. In the **Breakpoints** pane, click the **Pause on exceptions** button, which turns blue when enabled. 2. By default, it only pause on uncaught exceptions. If you also want to pause on caught exceptions, check the **Pause On Caught Exceptions** checkbox. ![Pause on exceptions](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/pause-on-exceptions.png) :::warning Currently, the PrimJS engine does not distinguish between caught and uncaught exceptions, and all exceptions will cause a pause. In contrast, the V8 engine distinguishes between caught and uncaught exceptions. ::: ## Debug JavaScript By default, you can only debug the background thread. If you need to debug the main thread, please refer to the section [Debug the Main Thread](#debug-the-main-thread). ### Step Through Code Once your code is paused, step through it, one expression at a time, investigating control flow and property values along the way. ![Pause](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/pause.png) #### Step Over Please refer to [Step over line of code | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#step-over). #### Step Into Please refer to [Step into line of code | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#step-into). #### Step Out Please refer to [Step out of line of code | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#step-out). #### Run All Code Up to a Certain Line Background V8 Only Please refer to [Run all code up to a certain line | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#continue-to-here). #### Resume Please refer to [Resume script execution | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#resume). ##### Terminate Execution Background V8 Only To stop your script's execution after a pause, click and hold the **Resume** button and then select **Terminate script execution** button. ![Terminate script execution example](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/terminate-execution-example.png) For example, in this case, select **Terminate script execution**, DevTool will stop executing the script, the rest of the code in `add` will not be executed, and you will see that the value of `count` will not change. ### View and Edit Properties Please refer to [View and edit local, closure, and global properties | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#scope). ### View the Current Call Stack When paused on a line of code, you can use the **Call Stack** pane to view the call stack that got you to this point. Select an entry in the pane to jump to the line of code where the function was called. A blue arrow icon represents the currently highlighted code. ![Call stack](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/call-stack.png) #### Copy Stack Trace Right-click anywhere in the **Call Stack** pane and select **Copy stack trace** to copy the current call stack to the clipboard. ![Copy stack trace](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/copy-stack-trace.png) For example, in this case, the copied stack trace is as follows: ``` add (App.tsx:11) publishEvent (tt.js:148) ``` ### Ignore Scripts Background V8 Only Ignore certain scripts during debugging to skip them. When ignored, scripts will be hidden in the **Call stack** pane, and you will never step into functions from ignored scripts while stepping through code. #### Ignore a Script from the Editor Pane Please refer to [Ignore a script from the Editor pane | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#editor-ignore-list). #### Ignore a Script from the Call Stack Pane Please refer to [Ignore a script from the Call Stack pane | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#call-stack-ignore-list). #### Show ignore-listed frames If you need to view the complete call stack, click **Show ignore-listed frames** in the **Call Stack** pane. The ignored frames will be expanded but displayed in gray. ![Show ignore1](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/show-ignore1.png) ![Show ignore2](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/show-ignore2.png) #### Unignore Scripts To unignore scripts, follow the same steps as above and select **Remove from ignore list**. You can also unignore scripts through the prompt at the top of the **Code Editor Pane**. ![Remove ignore](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/remove-ignore.png) ### Watch the Values of Custom JavaScript Expressions Please refer to [Watch the values of custom JavaScript expressions | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#watch). ### Inspect and Edit Scripts In the **Code Editor Pane**, you can browse and edit code. #### Make Minified Files Readable Click the `{}` button in the bottom status bar of the editor, and the **Sources** panel will present the minified file in a more readable format to improve readability. ![Format before](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/format-before.png) ![Format after](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/format-after.png) #### Edit Scripts Background V8 Only Please refer to [Edit a script | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#edit). :::warning You can only modify compiled JavaScript files. The original files reversed through SourceMap cannot be modified, and changes will not be saved after reloading the page. ::: #### Search and Replace Text in Scripts Please refer to [Search and replace text in a script | Chrome DevTools](https://developer.chrome.com/docs/devtools/javascript/reference#search). After replacing, you need to manually save the script, as referenced in [Edit Scripts](#edit-scripts). ## Debug Original Code with Source Maps Background Only After [configuring SourceMap](/api/rspeedy/rspeedy.output.sourcemap.md), you can directly debug the original code you author in the Sources panel. ### Check If Source Maps Loaded Successfully #### Check Load Status When you open DevTool, it attempts to load source maps, if any. After loading successfully, the files in the **File Navigator Pane** with orange folders are the original source files. ![Original files](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/original-files.png) If loading fails, the **Console** logs an error similar to the following. ![Sourcemap fail](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/sourcemap-fail.png) When you open any compiled file, DevTool will notify you if it found the source map. ![Sourcemap detected](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/sourcemap-detected.png) #### Load a Source Map Manually You can manually load a source map: 1. Open the compiled file, right-click anywhere in the **Code Editor Pane**, and select **Add source map**. Add sourcemap1 2. Enter the source map URL in the textbox, then click **Add**. Add sourcemap2 ### Debug with Source Maps 1. In the original file, you can [set breakpoints](#pause-code-with-breakpoints) as you normally would, or you can set breakpoints in the compiled file, and DevTool will automatically jump to the original file. 2. After triggering a breakpoint and pausing, the **Call Stack** pane will display the name of the original file. Original file stack 3. The bottom status bar of the **Code Editor Pane** will show a link to the compiled file it points to. Click it to jump to the corresponding file. Sourcemap from ## Debug the Main Thread Debugging the main thread is similar to debugging the background thread, but it requires some additional steps to enable. ### Preparation To debug the main thread, you need to start the project in **dev** mode using rspeedy. You can check the following two items to ensure preparation is done: 1. A `debug-info.json` file is generated in the [intermediate output directory](/api/rspeedy/rspeedy.distpath.intermediate.md). Debug info 2. In the `tasm.json` file of the [intermediate output directory](/api/rspeedy/rspeedy.distpath.intermediate.md), the `templateDebugUrl` field is a valid URL pointing to the `debug-info.json` file mentioned in step one. Refer to [rspeedy.dev.assetprefix](/api/rspeedy/rspeedy.dev.assetprefix.md). Template debug url ### Enable Main Thread Debugging Hover over the position shown in the image below, then enable **Main Thread Debugging**. This is a non-persistent global switch that takes effect for all pages during a single APP run. The switch state will be reset after closing and restarting the APP. ![Main thread debugging](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/main-thread-debugging.png) To enable main thread debugging: 1. Turn on the switch, then open the page you want to debug. Or open the page you want to debug, turn on the switch, and then [reload](/guide/devtool/panels/elements-panel.md#introduction-to-the-preview-panel) the page. 2. The `main-thread.js` file of the main thread will be displayed in the **File Navigator Pane** (formerly known as `lepus.js`). 3. In the **Threads** pane, switch to **Main** to start debugging. ![Main target](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/sources/main-target.png) In the **Threads** pane, you can switch between debugging the main thread or the background thread. The blue arrow icon represents which context is selected. The currently paused thread will be marked as **paused**. ## Compatibility ### Breakpoints **Error:** No compatibility data found for `devtool.panels.sources.breakpoints` ### JS Engine **Error:** No compatibility data found for `devtool.panels.sources.JSEngine` --- url: /guide/devtool/panels/layers-panel.md --- .full_image { width: 800px; margin: 20px; } # Layers Panel The **Layers** panel helps you understand the composition of Lynx pages and how the framework presents content, analyzing its 3D layers to discover rendering issues. ## Overview Use the **Layers** panel to perform the following operations: - View page layers - Inspect page layers ### Open the Layers Panel To open the Layers panel, follow these steps: 1. Press **Command+Shift+P** to open the command menu. 2. Start typing **Layers**, select **Show Layers**, and then press **Enter**. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/layers/open-layers.png) Or, select the More options in the upper right corner -> **More tools** -> **Layers**. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/layers/open-layers2.png) ## View page Layers The leftmost part of the **Layers** panel lists all the rendered layers of the Lynx page in an expandable tree. This tree structure is updated as the mobile **Lynx** page is updated. Layers are identified by their **CSS** selector or a number (followed by the layer size in pixels). ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/layers/view-layers.png) Hover the mouse over a layer to highlight it on the Lynx page preview window. A tooltip will be displayed on the page preview, which contains the following information: - The selector of the layer - The size of the layer (in pixels) ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/layers/view-layers-screen.png) ## Inspect page Layers Click on a layer to view more information in the **Details** pane. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/layers/check-layer-details.png) Depending on the layer, the following information will be displayed: - Size - Compositing reason - Memory estimate - Number of draws The following figure shows the stacking and arrangement of the layers of this **Lynx** page. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/layers/layer-3d-view.png) To move the chart, do the following: - Use **WASD** to move the chart. Press W to pan up, A to pan left, S to pan down, and D to pan right. - Click the **Pan Mode** icon to move along the X and Y axes. - Click the **Rotate Mode** icon to rotate along the Z axis. - Click **Reset Transform** to reset the chart to its original position. - Scroll the mouse wheel up to zoom in. - Scroll the mouse wheel down to zoom out. To view the element corresponding to the layer in the **Elements** panel, right-click on the corresponding layer in the chart or layer tree, and then click **Reveal in Elements panel**. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/layers/layer-element.png) ## Compatibility **Compatibility Table** **Query:** `devtool.panels.layers` **Platform Support** | Platform | Version Added | Notes | |----------|---------------|-------| | Android | 3.0 | - | | iOS | 3.0 | - | | HarmonyOS | 3.0 | - | **Description:** View page layers --- url: /guide/devtool/panels/preact-devtools-panel.md --- .full_image { width: 800px; margin: 20px; } # Preact Devtools Panel The **Preact Devtools** panel helps you inspect the hierarchy of ReactLynx components, displaying information such as component props, state, and file paths. The Preact Devtools panel is based on the [Preact Devtools](https://preactjs.github.io/preact-devtools/) web browser extension and offers the same user experience. ## Overview With the **Preact Devtools** panel, you can: - View the component hierarchy of ReactLynx - Modify props or state and see real-time updates - Use ClickToComponent to navigate to component source code - Use the Profiler to analyze application performance ### Open the Preact Devtools Panel In the DevTool window, click the Preact Devtools tab in the main panel to open it. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/open-preact-devtools.jpg) Note: Before using the **Preact Devtools** panel, you need to import [`@lynx-js/preact-devtools`](https://www.npmjs.com/package/@lynx-js/preact-devtools) at the **first line** of your ReactLynx application's entry file: ```js import '@lynx-js/preact-devtools'; ``` ## View Component Hierarchy The left side of the **Preact Devtools** panel shows the complete hierarchy of the ReactLynx application, while the right side displays detailed information about the selected component, including its props, state, and file path. Hover over a component to highlight its corresponding box model in the preview window on the left. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/component-hierarchy.jpg) ## View and Modify Props or State On the right side of the **Preact Devtools** panel, you can directly modify the props or state of components, and the ReactLynx application's UI will update in real-time. As shown below, we changed the state used for counting in the `Counter` component from `1` to `2`, and the counter UI on the page also changed from `1` to `2`. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/state-modify.jpg) ## ClickToComponent for Source Navigation We integrated the [ClickToComponent](https://github.com/ericclemmons/click-to-component) into the **Preact Devtools** panel. By clicking the file path under **source location**, VSCode will automatically open the file containing the component and navigate to the corresponding line and column. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ClickToComponent.jpeg) ## Profiler The Profiler panel supports performance analysis of ReactLynx component rendering time on background thread. Click the button in the top-left corner to start recording, let the application run for a while, and then click the button again to stop. You will then get information about all Preact commits during that time, as well as the rendering time of each component within the same commit. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/preact-devtools-profiler.jpg) --- url: /guide/devtool/trace.md --- .full_image { width: 750px; margin: 20px; } # Trace Trace is a performance analysis tool. With Trace, you can obtain a detailed rendering workflow of Lynx pages, making it easier to locate, analyze, and fix issues such as jank and long-running tasks. ## Core Capabilities of Trace ### 1. Visualizing the Rendering Pipeline Trace provides a complete timeline view of the entire page process, detailing each stage: script execution, element construction, layout calculation, final rendering and others. With Trace, you can: - Clearly see the time distribution for each key stage (such as layout and painting); - Directly identify bottleneck nodes and performance issues; - Detect redundant or repeated rendering tasks to optimize page performance. This end-to-end visualization is especially valuable for page optimization and tuning complex page performance. ![Trace Render Pipeline](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/trace_render_pipeline_latest.png) ### 2. Tracing Engine Execution Trace dives deep into the [Engine](/guide/spec.md#engine) and thoroughly records: - The sequence of task scheduling and queue switching - The full picture of event handling chains - Precise timing and relationships for API calls This information is useful for analyzing issues like asynchronous task congestion or event response delays. ![Trace Task Scheduling](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/trace-scheduling.png) ### 3. Rich Performance Analysis Tools Trace is a multidimensional performance analysis platform: - **[Element](/guide/spec.md#element) Analysis**: Track changes in the Element tree structure, identify high-frequency or heavy node operations, and uncover performance risks in your page structure. ![Element](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/element-render-analysis-latest.png) - **[NativeModules](/guide/spec.md#native-module) Call Analysis**: Monitor detailed NativeModules call processes, clearly display the timing and parameters of each NativeModule call, and pinpoint potential bottlenecks. ![NativeModules](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-flow.png) - **Smoothness Analysis**: Use flame charts of task durations to quickly locate the root causes of jank, helping you optimize animations and interactions. ![Trace Frame](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/trace-frame.png) ## Next Steps [Record Trace Learn how to record a trace ](/guide/devtool/trace/record-trace) ## Compatibility **Compatibility Table** **Query:** `devtool.trace` **Platform Support** | Platform | Version Added | Notes | |----------|---------------|-------| | Android | 3.4 | - | | iOS | 3.4 | - | | HarmonyOS | 3.4 | - | **Description:** Trace Tool --- url: /guide/devtool/trace/record-trace.md --- .full_image { width: 750px; margin: 20px; } # Record Trace This article aims to help you use DevTool to record Trace for Lynx pages. By following these steps, you will be able to capture detailed information about the page rendering process and engine execution, enabling effective performance analysis and problem diagnosis. ## Setup 1. Install [DevTool Desktop Application](/guide/devtool/panels.md#run-lynx-devtool-desktop-application). 2. Use the latest [LynxExplorer app](https://github.com/lynx-family/lynx/releases) or [integrate lynx dev version](/guide/start/integrate-lynx-dev-version.md) into your application. 3. Use a data cable to connect your device to your computer. ## Record Trace ### Select Trace Open the DevTool desktop application and choose `Trace` from the topbar. ![Select Trace Panel](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/select-lynx-trace-panel-latest2.png)### Select the app Pick the target app you want to profile from the available device list. ![Select Target App](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/select-target-app-latest.png)### Start recording Press the `Start` button to start recording trace. ![Start Trace](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/start-trace-latest.png)### Stop recording Click the `Stop` button to stop recording trace. ![Stop Trace](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/stop-trace-latest.png)### Analyze After stopping, the trace data will be displayed for analysis. ![Analyze Trace](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/analyze-trace-latest.png)### Download trace data(optional) If needed, click the `Download` button to save the trace file to your computer. ![Download Trace](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/download-trace-latest.png)### View or manage historical trace data Click **History** button to see previous trace data. ![Trace History](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/history-trace-latest.png)You can select a trace file to `view`, `download`, or `delete` as needed on Trace History panel. ### FAQ ##### After clicking `Start` button, the message "Current app does not integrate lynx and lynx-trace components with dev version, please integrate dev version first according to the following document" appears. - Use the latest [LynxExplorer app](https://github.com/lynx-family/lynx/releases), or refer to [integrate lynx dev version](/guide/start/integrate-lynx-dev-version.md) to integrate the dev version of lynx. ##### After clicking `Start` button, the message "Tracing already started" appears. - A trace recording is already in progress. Click `Stop` button to end it. ## Next Steps [Advance Usage Explore advanced features like recording startup trace, JavaScript profiling, and custom trace events ](/guide/devtool/trace/launch-trace) --- url: /guide/devtool/trace/trace-ui-basic-usage-guide.md --- .full_image { width: 750px; margin: 20px; } # Trace UI Basic Usage Guide This guide introduces the main UI structure and common operations to help you quickly get started with performance analysis. ## UI Structure Overview ![UI Structure Overview](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/trace-ui-structure-overiview-3-5.png) The Trace UI consists of the following key components: - Topbar: Includes a search box, SourceMap Decode, Focus LynxView, and other features. - Timeline: Displays the occurrence and duration of TraceEvents. Supports adding annotations (notes), measuring intervals, and more. - TrackTreeView: Organizes TraceEvents by track (typically one or more per thread), making it easy to distinguish between different threads/tasks. - DetailPanel: Shows detailed information about a selected TraceEvent, including parameters, duration, thread, process, and more. ## Common Operations ### Open a Trace File - Drag and drop a trace file directly onto the page for automatic parsing and display of trace. ### Zoom and Pan the Timeline - Zoom in/out: Use the `w` (zoom in) and `s` (zoom out) keys. - Pan left/right: Use the `a` (left) and `d` (right) keys. ### Search for TraceEvents ![Search for TraceEvents](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/search-trace-event.PNG) - Enter TraceEvent names, parameters, or keywords in the Topbar search box to quickly locate events. - Press the `f` key to jump to the currently selected search result. - Click the arrow at the end of the search box to jump to the previous/next result, or press Enter to move to the next result. - The Timeline will highlight the currently selected search result for easy identification. ### View TraceEvent Details - Click any TraceEvent in the Timeline to view its detailed parameters, duration, process, and thread information in the DetailPanel. ### Aggregate TraceEvents ![Aggregate TraceEvents](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/aggregate-trace-event.PNG) - Select a range of TraceEvents by dragging with the left mouse button across one or more tracks. Events will be aggregated by name. - Aggregated results are shown in the DetailPanel, including total duration, average duration, and occurrence count. - Click the column headers in the aggregation results to sort by name, total duration, average duration, or count. ### Add Timeline Annotations ![Add Timeline Annotations](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/add-timeline-annotations-latest.png) - Click on the Timeline to add a note at the corresponding position. Notes can be named for easier identification and navigation. ### Measure Time Intervals ![Measure Time Intervals](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/measure-time-intervals.png) - Click and drag on the Timeline to measure the interval between two points. This is useful for precisely analyzing the duration of tasks or operations. ### Focusing LynxView Trace data may contain events from multiple Lynx pages. Trace supports focusing on one or more specific Lynx pages, making it easier to locate and analyze performance issues for particular pages. #### 1. Click Focus LynxView button ![Force LynxView Button](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/focus-lynxview-trace.png)#### 2. Select the Lynx page(s) by URL ![Force LynxView Choose](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/focus-lynxview-choose-trace-latest.png)After selecting the Lynx page(s), Trace events from unselected pages will be grayed out. ![Force LynxView After Choose](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/focus-lynxview-after-trace-latest.png) ### Querying Lynx Engine Version Search for LynxEngineVersion or Version to find the Lynx Engine version. ![Lynx Engine Version](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/lynx-engine-version-trace.png) ### Querying the System Where the Trace Was Recorded 1. Enter a colon in the Topbar search box to enter SQL query mode; 2. Input the following SQL statement to query the system information where the Trace was recorded: ```sql select name, str_value from metadata where name like "system%"; ``` ![Trace System](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/record-trace-system-machine-trace.png) ## Additional Resources For a more detailed guide on using the Trace UI and its advanced features, please refer to the official Perfetto documentation: [perfetto-ui](https://perfetto.dev/docs/visualization/perfetto-ui) --- url: /guide/devtool/trace/launch-trace.md --- .full_image { width: 750px; margin: 20px; } # Record Trace During App Startup Trace supports recording trace during the app startup process, which is essential for analyzing and optimizing app's start performance. ### Click Startup Trace In the topbar, click the `Startup Trace` button. ![Startup Trace Configuration](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/startup-trace-config-latest.png)### Set duration (seconds) Enter the desired duration for recording trace during app startup. Press the `Enter` key to save your configuration. ![Startup Trace Duration](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/startup-trace-dur-latest.png)### Restart application Close and relaunch your app. Trace will automatically record trace during the startup period. ### Load startup trace After the app launches, choose target app, then click the `Load Trace` button in Lynx DevTool. ![Load Startup Trace](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/load-startup-trace-latest.png)### View the startup trace Once the trace data has loaded, you will see a visualization of the app’s startup process for detailed analysis. ![Load Startup Trace Success](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/load-startup-trace-success-latest.png) ### FAQ ##### After clicking `Load Trace` button shows: "Startup tracing is not enabled, cannot pull trace data" - Make sure to set a valid trace duration (greater than 0) in Startup Trace Configuration, then restart the app and click `Load Trace` button again. - Ensure a Lynx page is opened during or after the app startup process before clicking `Load Trace` button. ##### Clicking `Load Trace` button shows: "Startup tracing is in progress, please try again later" - The app startup trace is still being recorded. Please wait for the process to finish and try clicking `Load Trace` button again later. --- url: /guide/devtool/trace/js-profile.md --- .full_image { width: 750px; margin: 20px; } # Analysis JavaScript Trace supports recording detailed JavaScript profile data. A JavaScript Profile is a collection of stack traces sampled during execution, which is crucial for pinpointing performance hotspots and bottlenecks. This allows you to analyze [script](/guide/spec.md#javascript-script) execution performance. ### Click `JS Profile` and Select the correct scripts engine Based on the background thread scripts engine used by your Lynx page, choose the appropriate value in the JS Profile selection. - disable: Do not enable JavaScript profiling; - [primjs](/guide/spec.md#primjs-vm): Enable profiling for the PrimJS JavaScript engine. - [v8](/guide/spec.md#v8-javascript-core-quickjs-hermes-etc): Enable profiling for the V8 JavaScript engine. ![Choose JS Profile Type](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/choose-js-profile-type-latest.png)### Start recording Click `Start` button to begin recording trace. ![Start Recording JavaScript Profile](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/start-js-profile-trace-latest.png)### Stop recording When finished, click `Stop` button to end the recording. ![Stop Recording JavaScript Profile](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/stop-js-profile-trace-latest.png)### Decode JavaScript Profile Data using sourcemap (Optional) If your scripts is [minified](/api/rspeedy/rspeedy.minify.md), you can use [sourcemap](/api/rspeedy/rspeedy.sourcemap.md) to decode profile data. ![Decode JavaScript Profile](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/sourcemap-decode-latest.gif) ### FAQ ##### No JavaScript profile data after recording trace - Make sure you have selected the correct script engine before starting recording. - On iOS, ensure both Lynx DevTool and [PrimJS Debugging](/guide/start/integrate-lynx-devtool-advanced.md) are enabled. ##### No scripting resource found after clicking SourceMap Decode - Ensure you start trace recording before opening the Lynx page so that the script resource name can be captured. --- url: /guide/devtool/recorder.md --- # Recorder Recorder is a tool designed for the Lynx framework to facilitate the recording and replay of page runtime states. Its core functionality involves precisely capturing the complete state of a page at a specific moment and serializing it into a portable recording file for subsequent high-fidelity replay. ## What it does? Unlike traditional screen recording technologies, Recorder achieves "Deterministic Replay." During the replay process, the system not only reconstructs the complete UI hierarchy and rendering data from the time of recording but also preserves all page interactivity. Crucially, it achieves temporal consistency by hijacking and mocking time-related APIs, ensuring that time-sensitive operations within the replayed environment yield the same results as they did during the original session. Recorder features cross-device replay capabilities. Through its integrated Replayer decoder, a recording file can be parsed and replayed on any device that supports the Lynx framework, regardless of hardware model or operating system. For instance, a session recorded on an iPad can be accurately reproduced on an iPhone, an Android device, or even on desktop and TV applications built with the same Lynx technology stack. ## What can you use it for? As an application's feature set expands, Lynx pages often become tightly coupled with the native application environment. This includes dependencies on custom NativeModules or private cloud services for data and resource management. ![device-connect](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/recorder_main_page.png) When diagnosing complex issues, this strong coupling becomes a major impediment to external collaboration and debugging, leading to problems such as build failures, complex authentication procedures, and inaccessible backend services. In such scenarios, traditional methods like code review are inefficient, while source-level debugging is often impractical due to environmental discrepancies. A core design objective of Recorder is to decouple the page from its private environment. A recording file encapsulates all information required for the page to run, including the view hierarchy, data state, and responses from asynchronous requests. During replay, all external dependencies are simulated using the data contained within the recording file, eliminating the need for any live network requests. This makes the debugging process entirely independent of the original production environment. Technical Advantages: - Environment-Agnostic: Debuggers do not need to compile the original application, configure complex authentication, or set up specific development environments. - Self-Contained Context: A single recording file contains all the necessary context to reproduce an issue. - Efficient Collaboration: The party reporting the issue simply provides a recording file, allowing the recipient to debug in a standardized replay environment. This significantly reduces communication overhead and the difficulty of issue reproduction. ## Try Recorder Recorder's record and replay capabilities are already integrated by default in LynxExplorer. You can follow the steps below to experience them. ### Download and install LynxExplorer Please visit [LynxExplorer](/guide/start/quick-start.md#prepare-lynx-explorer) and follow the documentation to run LynxExplorer app locally. ### Start record 1. Connect the device to your PC via USB, and select the target application in the Tab. ![device-connect](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/recorder-device-connect.png)2. Enter the Recorder plugin dashboard and click the Start button to activate. ![start-record](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/start-record.png)3. Operate on your phone, navigate to the target page you want to record and interact with it until it reaches the desired state. :::note It is especially important to note that, in order to ensure the completeness of the recorded page data, please make sure to click Start **before** navigating to the target page. ::: Here is a sample page that you can access by scanning the QR code or entering the link in LynxExplorer. **This is an example below: gallery** **Bundle:** `dist/GalleryComplete.lynx.bundle` | Web: `dist/GalleryComplete.web.bundle` ```tsx {6} import { root } from "@lynx-js/react"; import { furnituresPictures } from "../Pictures/furnitures/furnituresPictures.jsx"; import Gallery from "./Gallery.jsx"; function GalleryComplete() { return ; } root.render(); ``` 5. Click the Stop button to end the record. After stopping the record, the Recorder dashboard will automatically retrieve the recorded artifact from your device and provide a preview image to help you easily identify the target page. ![end-record](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/end-recordv2.png)### Page Replay 1. Artifact Hosting The Lynx DevTool Application only provides local file hosting for each recorded artifact. If you need to share the record artifact or access it across devices, you will need to host the artifact separately. 2. Replay using LynxExplorer Here is a sample recorded artifact that you can replay by scanning the QR code or entering the link in LynxExplorer. copy-link :::note It is especially important to note that if you are using a self-hosted artifact URL for replay, you need to append the Recorder Header to the artifact URL to indicate that the artifact is from Recorder. Original artifact URL: ```txt https://lynx.recorder.artifacts/LynxRecorder.json ``` Append the Recorder Header: ```txt file://lynxrecorder?url=https://lynx.recorder.artifacts/LynxRecorder.json ``` ::: LynxExplorer can fully reproduce the recorded page. ![replay-show](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/replay.png) ## Integrate Recorder into your own application ### Switch to the dev version Since the record and replay process of Recorder covers the entire rendering process of Lynx and requires support from the underlying Lynx engine, we have released a separate dev version apart from the official Lynx release to prevent these record-related codes from affecting production environments. If you want to integrate Recorder, you need to change the version numbers of both Lynx and LynxDevtool in your project to the corresponding dev versions. [integrate lynx dev version](/guide/start/integrate-lynx-dev-version.md) ### Record capability integration The record capability can be integrated simply by using the dev version, with no additional adaptation required. ### Replay capability integration Recorder provides a highly encapsulated replay module, which is integrated into your application together with LynxDevTool. You only need to provide a simple entry point as described below. Choose your target platform to view the specific integration steps: iOSAndroidHarmonyOS1. In the Podfile, additionally include LynxRecorder for the LynxDevtool module. ```ruby pod 'LynxDevtool', lynxDevVersion, :subspecs => [ 'Framework', 'LynxRecorder' ] ``` 2. Use LynxRecorderViewController to replay the recorded page. :::note Swift integration considerations LynxRecorder is written in Objective-C. If you want to call its methods from a Swift project, you need to provide a corresponding bridge file as described in [Importing Objective-C into Swift](https://developer.apple.com/documentation/swift/importing-objective-c-into-swift). Add the following content to the bridge file: ```objective-c title="BridgeFile" #import ``` ::: **Objective-C** ```objective-c #import LynxRecorderViewController *recorderVC = [[LynxRecorderViewController alloc] init]; recorderVC.url = @"file://lynxrecorder?url=https://lynx.recorder.artifacts/LynxRecorder.json"; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:recorderVC]; [self presentViewController:nav animated:YES completion:nil]; ``` **Swift** ```swift let recorderVC = LynxRecorderViewController() recorderVC.url = "file://lynxrecorder?url=https://lynx.recorder.artifacts/LynxRecorder.json" present(recorderVC, animated: true, completion: nil) ``` 1. Add a Recorder page entry **Java** ```java import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import com.lynx.devtool.recorder.LynxRecorderActivity; import com.lynx.devtool.recorder.LynxRecorderEnv; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); buildLynxRecorderView(); } private LynxView buildLynxRecordView() { String url = "file://lynxrecorder?url=https://lynx.recorder.artifacts/LynxRecorder.json" Intent intent = new Intent(this, LynxRecorderActivity.class); intent.putExtra(LynxRecorderEnv.getInstance().mUriKey, url); startActivity(intent); } } ``` **Kotlin** ```kotlin import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle import com.lynx.devtool.recorder.LynxRecorderActivity import com.lynx.devtool.recorder.LynxRecorderEnv class MainActivity :Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) buildLynxRecorderView() } private fun buildLynxRecordView() { val intent = Intent(this, LynxRecorderActivity::class.java) val url = "file://lynxrecorder?url=https://lynx.recorder.artifacts/LynxRecorder.json" intent.putExtra(LynxRecorderEnv.getInstance().mUriKey, url) startActivity(intent) } } ``` 2. Register LynxRecorderActivity in the app manifest file. ```xml // AndroidManifest.xml ``` lynx\_devtool provides a highly encapsulated `LynxRecorderView` component that enables page replay by simply passing in the recorded artifact URL. ```tsx import { LynxRecorderView } from '@lynx/lynx_devtool'; build() { Column() { LynxRecorderView({ url: 'file://lynxrecorder?url=https://lynx.recorder.artifacts/LynxRecorder.json' }) }.size({ width: '100%', height: '100%' }) } ``` ## ## Compatibility **Compatibility Table** **Query:** `devtool.recorder` **Platform Support** | Platform | Version Added | Notes | |----------|---------------|-------| | Android | 3.4 | - | | iOS | 3.4 | - | | HarmonyOS | 3.5 | - | **Description:** Lynx Recorder Support --- url: /guide/devtool/handle-errors.md --- # Handle Errors in Lynx ## Recognize Errors in Tools When you are developing Lynx apps, you may encounter various types of errors that are designed to notify you the unexpected situations that have just happened. For example, if the bundle file is broken, you'll see: ![Bundle is broken](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/error_invalid_bundle.png) Also, sometimes when your JavaScript code contains an TypeError: ![Syntax error](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/error_syntax.png) Sometimes, a [native module](/guide/spec.md#nativemodules) is called with a wrong number of arguments: ![Wrong arguments](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/error_wrong_args.png) With DevTool or LogBox, you shall easily recognize them. ## LogBox You've already seen it above - the in-app tool that displays errors. ![LogBox notification](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/logbox_message.jpeg)![LogBox red](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/lynx-website/assets/logbox_box.png) It's convenient for a quick overview and lets you know what's happening. However, for development purposes, we recommend using DevTool Desktop Application with more features and a better interactive experience. ## Handle Errors in Code Now let's try to do something with the errors. You may want to have an overall understanding of the [errors](/api/errors/lynx-error.md) first if you want to handle them in your code. And then, here is an example of handling error [301](/api/errors/error-code.md#eb_resource_image-301) in code: ```tsx const ImageErrorExample = () => { const [isImageError, setImageError] = useState(false); const handleImageError = (event: ErrorEvent) => { setImageError(true); console.log('Image loaded error:', JSON.stringify(event)); }; if (isImageError) { return ; } return ( ); }; ``` In order for developers to quickly categorize an error and figure out how to deal with it, we have summed up all of them and made a [list](/api/errors/error-code.md), including the error code, description, level, fix suggestion, etc. Just help yourself! ## Compatibility **Compatibility Table** **Query:** `devtool.logbox` **Platform Support** | Platform | Version Added | Notes | |----------|---------------|-------| | Android | 3.0 | - | | iOS | 3.0 | - | | HarmonyOS | 3.4 | - | **Description:** Shows logbox details --- url: /guide/performance/analysis-performance.md --- # Performance Analysis This section guides you through performance analysis methodologies, from diagnosing rendering latency and instrumenting business logic to optimizing page fluency and memory usage, enabling comprehensive smoothness and stability improvements. ## Rendering Latency Analysis ### Record Trace data Use [Devtool](/guide/devtool/trace/record-trace.md) to record Trace data. The Trace is presented on a [timeline](/guide/performance/analysis-performance/trace-track.md), capturing the full Lynx rendering pipeline, which lays the foundation for subsequent analysis. ### Analyze rendering latency On the [Rendering-time analysis](/guide/performance/analysis-performance/analysis-render-process.md) page, combine Trace data to review first-frame rendering and page update flows, pinpoint time-consuming stages and performance bottlenecks along the rendering path. The [NativeModule invocation](/guide/performance/analysis-performance/analysis-native-module.md) page explains the complete execution flow of NativeModule calls to help you analyze them efficiently. ### Add custom instrumentation Trace allows developers to add [custom Trace instrumentation](/guide/performance/analysis-performance/trace-api.md). Insert Trace events into key business flows, component lifecycles, and asynchronous tasks to visualize them on the Trace UI timeline, enabling precise tracking and localization of potential bottlenecks. ## Memory Analysis The [Memory analysis](/guide/performance/analysis-performance/analysis-memory.md) page describes how to use Trace and IDE tools to analyze memory peaks, growth trends, and abnormal leaks, helping developers discover and fix memory issues early to improve application stability. ## Fluency The [Fluency analysis](/guide/performance/analysis-performance/analysis-fluency.md) page explains how to analyze dropped frames and jank, enabling developers to quickly locate and optimize page interaction performance. --- url: /guide/performance/analysis-performance/analysis-render-process.md --- .full_image { width: 750px; margin: 20px; } # Render Time Analysis This document aims to help developers master how to accurately locate each execution stage in a Trace, thereby enabling effective analysis of specific performance issues. ## Analyze render time with Trace Frontend pages can [flag Pipeline](/guide/performance/monitor-performance/timing-flag.md) for key components. When the component finishes rendering on screen, the [Timing Track](/guide/performance/analysis-performance/trace-track.md) in Trace generates a bubble at the corresponding timestamp to mark the key stage. A Lynx page generally has two render types: first-frame rendering and update rendering. Internally, the Lynx SDK produces [LoadBundleEntry](/api/lynx-api/performance-api/performance-entry/load-bundle-entry.md) and [PipelineEntry](/api/lynx-api/performance-api/performance-entry/pipeline-entry.md) to describe stage durations in each process. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/timing_demo_trace_overview.png) Click a bubble to open a details panel showing the sub-stage timeline and the duration of each sub-stage in the render process. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/timing_demo_trace_detail_panel.png) The following sections detail both render types and their constituent stages, including the corresponding Trace event names, so you can locate and troubleshoot issues via Trace events. ## First-frame render time analysis First-frame rendering primarily consists of LoadBundle and Paint: ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/timing_demo_trace_load_bundle.png) ### LoadBundle This stage corresponds to `Timing::Mark.loadBundleStart` and `Timing::Mark.loadBundleEnd` in Trace. It mainly parses the bundle and performs the initial screen render. It can be broken down into six sub-stages: #### Parse This stage corresponds to `Timing::Mark.parseStart` and `Timing::Mark.parseEnd` in Trace. It parses the binary bundle into in-memory data structures. The bundle contains a header and multiple sections; the JS source section runs on the [background thread](/guide/spec.md#background-thread-aka-off-main-thread), mapped to `Timing::Mark.loadBackgroundStart` and `Timing::Mark.loadBackgroundEnd` in Trace. #### MTS Render This stage corresponds to `Timing::Mark.mtsRenderStart` and `Timing::Mark.mtsRenderEnd` in Trace. It executes [Main Thread Script (MTS)](/guide/spec.md#main-thread-script-or-mts) to build the Element Tree. #### Resolve This stage corresponds to `Timing::Mark.resolveStart` and `Timing::Mark.resolveEnd` in Trace. It traverses the element tree, computes a layout node tree from node attributes and styles, and generates [UI Paint OP](/guide/spec.md#platform-ui-paint-operations-or-ui-paint-op). #### Layout This stage corresponds to `Timing::Mark.layoutStart` and `Timing::Mark.layoutEnd` in Trace. It traverses the layout node tree produced during Resolve, computes node positions and sizes, and generates [UI Layout OP](/guide/spec.md#platform-ui-layout-operations-or-ui-layout-op). #### Paint UI OP Execute This stage corresponds to `Timing::Mark.paintingUiOperationExecuteStart` and `Timing::Mark.paintingUiOperationExecuteEnd` in Trace. It executes the UI Paint OP generated during Resolve. #### Layout UI OP Execute This stage corresponds to `Timing::Mark.layoutUiOperationExecuteStart` and `Timing::Mark.layoutUiOperationExecuteEnd` in Trace. It executes the UI Layout OP generated during Layout. ### Paint The system drawing stage measures, lays out, and paints the platform UI tree. The end of painting maps to `Timing::Mark.paintEnd` in Trace. ## Update render time analysis Update renders are primarily driven by the background thread to update and draw the UI tree. The main difference from first-frame rendering lies in [Framework Rendering](/guide/spec.md#framework-rendering). Using ReactLynx 3 as an example, we highlight the differing parts: ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/timing_demo_trace_pipeline_entry.png) ### Diff Changes This stage corresponds to `Timing::MarkFrameWorkTiming.diffVdomStart` and `Timing::MarkFrameWorkTiming.diffVdomEnd` in Trace. It performs component diff on the background thread. ### Pack Changes This stage corresponds to `Timing::MarkFrameWorkTiming.packChangesStart` and `Timing::MarkFrameWorkTiming.packChangesEnd` in Trace. It serializes diff results and sends them to the [main thread](/guide/spec.md#main-thread-or-lynx-main-thread). ### Parse Changes This stage corresponds to `Timing::MarkFrameWorkTiming.parseChangesStart` and `Timing::MarkFrameWorkTiming.parseChangesEnd` in Trace. It parses the serialized data on the main thread. ### Patch Changes This stage corresponds to `Timing::MarkFrameWorkTiming.patchChangesStart` and `Timing::MarkFrameWorkTiming.patchChangesEnd` in Trace. It applies the diff results on the main thread and updates the Element Tree. Subsequent render stages are similar to the first-frame process: Resolve, Layout, Paint UI OP Execute, Layout UI OP Execute, and Paint. ## Next Steps [ReactLynx Render Pipeline Understanding the ReactLynx Rendering Pipeline ](/guide/performance/analysis-performance/reactlynx-render-process) --- url: /guide/performance/analysis-performance/analysis-fluency.md --- .full_image { width: 750px; margin: 20px; } # Fluency Analysis When scrolling occurs, the system attempts to draw the next frame at a fixed cadence; any noticeably longer draw interval will be perceived by users as a “stutter” or “jitter.” These momentary long frames are the source of “dropped frames.” Scrolling is the most cadence-sensitive scenario, because backgrounds, text, and images are continuously moving; once the beat becomes unstable, the eye immediately notices. ## Offline Trace analysis Offline Trace aims to reconstruct the frame timing of a scrolling interaction, tie “which segment dropped frames” to “why it dropped,” and produce actionable fixes. For Trace capture, see [Record Trace](/guide/devtool/trace/record-trace.md) and ensure the capture covers the full scrolling time window and realistic scroll speed. ### How Trace displays dropped frames In the Trace webpage, frame render time labels were added at the start of `Choreographer#doFrame` (currently Android only). These labels display dropped-frame situations throughout the entire system render process. By observing these labels, you can visually identify render-time anomalies. Frame render time is computed by summing the time spent in `Choreographer#doFrame` on the UI thread and `DrawFrames` on the `RenderThread`. If the total is ≥ 32 ms, the label is red; if the total is ≥ 16 ms and \< 32 ms, the label is orange; if the total is \< 16 ms, the label is green. For offline analysis, treat frames marked red or orange as the tasks most likely causing dropped frames and analyze them preferentially. ![Frame Rendering Time](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/20250929-163554.jpeg) ### Locating dropped-frame segments during scrolling - On the timeline, look for frames that clearly exceed the cadence (for example, consecutive red labels) and mark them as “long-frame segments.” - Analyze frame by frame to confirm whether the cause is UI-thread congestion, delayed render submission, or resource loading/decoding inserted into the critical path. - Associate long-frame segments with UI elements (large images, complex list items, card animations) to form actionable ties to what users see. ### Root-cause patterns and identification - UI thread blocking: layout and measurement stacking, or I/O running on the UI thread. - Heavy layout and drawing: deep hierarchies, frequent reflow, or large-area repaint; concentrated text and image size computation; complex `Canvas` drawing. Symptom: a single frame’s computation time is unusually long and repeats with scroll. - Image and resource loading: decode, downsample, and texture upload blocking critical frames; async not truly isolated, or missing placeholders/preload. Manifestations: `GPU` or `RenderThread` queues back up, correlated with image size or count. ### Common investigation suggestions - Reduce UI-thread workload: move non-essential computation and I/O off the UI thread; spread layout and drawing across frames to avoid “everything in a single frame.” - Optimize layout depth and measurement: control hierarchy depth and constraints complexity; avoid repeated measurement; use compositing layers to reduce unnecessary repaint. - Image and resource governance: constrain dimensions and size, use placeholders and preload; move decode and texture upload earlier or run in parallel; load on demand and reuse caches. - JS and bridge: split long tasks and reduce synchronous calls; use throttling and batching to avoid high-frequency messages overwhelming the UI cadence. --- url: /guide/performance/analysis-performance/analysis-memory.md ---  .full_image { width: 750px; margin: 20px; } # Memory Usage Analysis Memory analysis helps discover leaks and abnormal usage within Lynx pages and ensures stability. You can use Trace Memory Track and IDE tools, to comprehensively monitor and troubleshoot memory-related performance issues. ## Deep analysis with Trace Starting from 3.4, Trace supports Memory Track, which shows Lynx page memory usage trends over time. Each Memory Track represents one Lynx page’s memory changes. ![Memory Track](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/memory-track-latest.png) Click the Memory Track curve to see the page’s total memory usage and the memory used by Element, background scripting engine, main-thread scripting engine, images, etc. ![Memory Track Detail](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/memory-track-details.png) ## Analyze memory with IDEs This section briefly introduces how to use Android Studio or Xcode’s built-in tools to debug apps and view performance metrics. iOSAndroidThis section introduces how to analyze memory using Xcode. ### Analyze (static check) Choose Product > Analyze or press Shift + Command + B to run automatically. Analyze uses syntax and memory context to find potential issues. It mainly detects four categories: 1. Logic errors: dereferencing null pointers, uninitialized variables. 2. Memory management errors: e.g., memory leaks. 3. Declaration errors: variables never used. 4. API call errors: missing libraries or frameworks. :::tip 📌 Analyzer makes compiler-driven judgments and is not always accurate. If you see a report, review surrounding code; some cycle references that cause memory leaks cannot be identified by Analyzer. Analyzer makes compiler-driven judgments and is not always accurate. If you see a report, review surrounding code; some cycle references that cause memory leaks cannot be identified by Analyzer. ::: ### Leaks (dynamic check) Xcode > Open Developer Tool > Instruments > Leaks ![Leaks dynamic check](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-12.png)Choose Leaks, then specify the device and app to debug. In this example, an iPhone SE2 (iOS 14.5) simulator debugs the LynxExample app. Click the start button to check for memory leaks. ![Leaks Start Button](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-13.png)After starting, operate in the simulator or on device. If the Leaks window shows issues, jump to the corresponding code to debug. You can pause to inspect memory info shown in the chart. After debugging, you can save the results locally. ![Leaks Analysis Result](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-14.png)If a memory leak appears (example below), set the chart to Call Tree mode, then enable Invert Call Tree at the bottom of the window. Select any leak to jump to source code. ![Leaks Call Tree Mode](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-15.png)### Allocations (detailed allocation) Xcode > Open Developer Tool > Instruments > Allocations ![Allocations Tool](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-17.png)Similar to Leaks but focuses on detailed memory allocations. The lower list shows per-method memory usage. Check a graph to show a bar chart above. Click an item to view stack trace details; click the right-hand Stack Trace pane to jump into code and inspect allocations. ![Allocations Stack Info](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-18.png)The code section is highlighted and the allocation ratio is shown. ![Allocations](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-19.png)### This section introduces how to analyze memory using Android Studio. ### Profiler basics ![Profiler Tool](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-1.png)First click the highlighted area in the toolbar; Android Studio performs a Build and then opens the Profiler window. From there you can run tests—click the ‘+’ icon to select a device connected via adb and the process to test. ![Profiler Choose Process](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-2.png)Using a Xiaomi MIX (API 24), we run a \~45 s test and manually trigger a GC at 15 s. From left to right, the icons are GC, Capture Heap Dump, and manual Record (an option for devices below API 26 for memory analysis). ![Profiler Tool Record](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-3.png)Over 45 s, the device’s memory changes are as follows: ![Profiler Tool Record](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-4.png)In this example, after the manual GC, the app’s memory usage drops noticeably for a period. Heap dumps show which objects are using memory at the time of capture. Especially after long user sessions, heap dumps reveal objects that should no longer be in memory, helping identify leaks. After clicking the Heap Dump icon, Java memory may temporarily increase because dumping runs in the same process and needs memory to collect data. ![Profiler Memory Status](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-5.png)After capturing the heap dump, use the built-in analyzer. The table shows: - Allocations: number of allocations in the heap. - Native Size: total native memory used by this object type (Bytes). Visible on Android 7.0+ only. Some Java-allocated framework classNames (e.g., Bitmap) consume native memory. - Shallow Size: total Java memory used by this object type (Bytes). - Retained Size: total memory retained by all instances of this className (Bytes). ![Profiler Memory Detail](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-6.png)Click a className in the chart to open the Instance List window, then jump to source for inspection. ![Profiler Source Info](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-7.png)Or use the Reference window for more detailed checks and jump elsewhere for inspection. ![Profiler Source Transfer](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-8.png)After capturing a heap dump, you can also export hprof data from the SESSIONS window via Export Heap Dump. Use other tools (such as MAT) to further analyze the heap. ![Profiler Memory Dump](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-9.png)### Memory profiler On devices below API 26, you need to manually record a time window before running memory performance analysis. Use the filter options (red circle) such as Arrange by className. In the table, select a className to jump to the Instance View, then to Allocation Call Stack to view stack traces and jump to source. ![Memory profiler](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-10.png)### Symptoms of memory leaks - Many Full GCs during runtime. - During a specific thread’s execution, manual GC has little effect. - Many allocations and frees in a short time (memory graph jitter). ![Memory profiler](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-11.png)### Example leak and related analysis ```java final className LoadedInChildClassLoader { static final byte[] moreBytesToLeak = new byte[1024 * 1024 * 100]; private static final ThreadLocal threadLocal = new ThreadLocal<>(); public LoadedInChildClassLoader() { threadLocal.set(this); } } ``` #### Methods to trigger potential leaks Apply pressure to app code and try to force leaks while cross-checking with the memory profiler. One way to induce leaks is to let the app run for some time, then inspect the heap; leaks may accumulate near the top of allocation. The smaller the leak, the longer the app needs to run to reveal it. - Rotate the device repeatedly to trigger potential leaks. - Switch between the debug app and other apps across different Activity states (e.g., go Home then return). #### Possible causes of leaks - Long-running threads. - A thread loads a className via the ClassLoader. - A className allocates large memory. - A thread clears references to all custom classNames and the ClassLoader that loaded them. ## Performance testing with PerfDog PerfDog is a third-party tool for checking Android and iOS app performance and is easy to use. After connecting a device and choosing the app to debug, click the red circled plus button at the bottom right to add performance parameters before starting. ![PerfDog Tool](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-20.png) After the test, you can save data as a table and view performance parameters such as Jank count. Use the tool’s metrics as simple references to check app performance. ![PerfDog Save Data](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/ide-perf-21.png) --- url: /guide/performance/analysis-performance/analysis-native-module.md --- .full_image { width: 750px; margin: 20px; } # NativeModule Invocation NativeModule is the core communication bridge between JavaScript and native applications. By invoking a NativeModule, Lynx frontend pages can access and control native capabilities. This document together with code examples, details the invocation and execution flow of NativeModule within Trace. ## Deep analysis with Trace Since Lynx 3.2, the Trace page includes a NativeModule Track that aggregates all NativeModule invocations. Each item’s length reflects the overall duration from request initiation to completion. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-track.png) Basic display: click an item in the NativeModule Track to view a color-coded breakdown of rough durations for each stage of the invocation. Details panel: shows the method name, input parameters, and precise per-stage durations (including parameter conversion, platform logic, thread switches, and other key nodes). ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-timing-stage.png) ## Breakdown of NativeModule invocation details Below we use a frontend NativeModule call as an example to describe execution timing in Trace. ### Example code ```ts {4,5,6,7,10} NativeModules.bridge.call( 'setStorage', { data: { key: 'lynx_nativemodule_test', value: i, }, }, (res) => { console.log('setStorage res is: ', res); }, ); ``` ### Trace execution analysis ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-normal.jpeg) A NativeModule call can be divided into five main stages: #### 1. Parameter conversion This stage corresponds to `JSValueToPubValue` in Trace, where JS data (lines 4–7 in the code sample) is converted into native types. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-stage-1.png) #### 2. Platform-layer implementation This stage corresponds to the time period in the Trace from the start of `CallPlatformImplementation` to `NativeModule::PlatformCallbackStart`, where the native method executes the specific functionality and returns platform-layer data. Among them, the `CallPlatformImplemention` occurs on the [background thread](/guide/spec.md#background-scripting-thread-historically-known-as-js-thread), while the logic between the `CallPlatformImplementation` event and `NativeModule::PlatformCallbackStart` usually runs on other threads. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-stage-2.png) #### 3. Waiting for the background thread to execute the callback This stage corresponds to `NativeModule::PlatformCallbackStart` and `NativeModule::Callback` in Trace. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-stage-3.jpeg) #### 4. Result conversion This stage corresponds to `PubValueToJSValue` in Trace, where platform-layer return data is converted into JS arguments. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-stage-4.png) #### 5. Callback execution This stage corresponds to `InvokeCallback` in Trace, which executes JS code logic (line 10 in the sample). ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-stage-5.png) ## Special NativeModule calls Currently, JSB SDKs in the company return results via callbacks. Some NativeModule implementations return results directly to the Lynx SDK via callbacks on the background thread. Callback execution is not necessarily limited to the main thread—any thread may invoke and execute callback-related logic. ### Example code ```js {4,5,6,9} NativeModules.bridge.call( 'x.reportAppLog', { data: { eventName: 'lynx_nativemodule_test_event_name', }, }, (res) => { console.log('reportAppLog res is: ' + JSON.stringify(res)); }, ); ``` ### Trace execution analysis These special NativeModule calls can be divided into five main stages. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-special.jpeg) #### 1. Parameter conversion This stage corresponds to `JSValueToPubValue` in Trace, where JS data (lines 4–6 in the sample) is converted into native types. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-special-stage-1.jpeg) #### 2. Platform-layer implementation This stage corresponds to the time period in the Trace from `CallPlatformImplementation` to the start of `NativeModule::Callback`, where the native method executes the specific functionality and returns platform-layer data. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-special-stage-2.jpeg) #### 3. Result conversion This stage corresponds to `PubValueToJSValue` in Trace, where platform-layer return data is converted into JS arguments. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-special-stage-3.jpeg) #### 4. Callback execution This stage corresponds to `InvokeCallback` in Trace, which executes JS code logic (line 9 in the sample). ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-special-stage-4.jpeg) #### 5. Cleanup This stage corresponds to the time period in the Trace from the end of `InvokeCallback` to the end of `NativeModule::Invoke`, where platform-layer cleanup is performed and external registrants are notified that the NativeModule call has completed. ![](https://lf-lynx.tiktok-cdns.com/obj/lynx-artifacts-oss-sg/plugin/static/nativemodule-invoke-special-stage-5.jpeg) --- url: /guide/performance/monitor-performance.md --- # Performance Monitoring Overview This overview helps you quickly build a mental model and understand common workflows for performance monitoring. It explains how the [Performance API](/guide/performance/monitor-performance/performance-api.md) and [Timing Flag](/guide/performance/monitor-performance/timing-flag.md) work together to create a closed loop from marking to analysis: `Mark` → `Collect` → `Report` → `Analyze`. ## Mental Model and Closed-Loop Workflow The goal of monitoring is to ensure that critical moments in your page's lifecycle can be accurately recorded, reported, and analyzed, allowing you to quickly trace issues back to the source code or specific UI locations. Centered around the Lynx rendering pipeline and standardized events, we recommend the following closed-loop workflow: - **Mark**: Use a Timing Flag (`__lynx_timing_flag`) on critical UI nodes or key data updates to define the rendering pipelines you need to monitor. The special flag `__lynx_timing_actual_fmp` can additionally generate the ActualFMP metric. For details, see [Marking Rendering Pipelines](/guide/performance/monitor-performance/timing-flag.md). - **Collect**: Register a `PerformanceObserver` as early as possible on the front end (e.g., to listen for `metric.fcp`, `pipeline`, etc.), or consume events via asynchronous callbacks on the client side. For details, see [Collecting Performance Events](/guide/performance/monitor-performance/performance-api.md). - **Report**: Report key metrics and events to your data platform using consistent naming and fields. - **Analyze**: Analyze production data, using `PipelineEntry.identifier` (the Timing Flag) and event timelines to trace problems back to the corresponding modules or data flows for debugging and review. Recommended minimum set of tracking points (for a basic production setup): - **metric**: `fcp` (required), `actual_fmp` (when you need to measure the "first meaningful data paint") - **pipeline**: `loadBundle` (for the initial screen rendering), and the pipelines associated with your critical Timing Flags. ## Feature Positioning ### [Performance API](/guide/performance/monitor-performance/performance-api.md) - **Positioning**: A standardized performance interface covering four event types: `init`, `metric`, `pipeline`, and `resource`. - **Triggering and Retrieval**: `PerformanceEntry` objects are generated by the Lynx Engine. The front end subscribes to them via `PerformanceObserver.observe([...])`, and the client receives them through the `onPerformanceEvent(entry)` callback on an asynchronous thread. - **Typical Uses**: - Obtaining standard metrics for initial screen rendering and key updates (FCP, ActualFMP). - Combining multiple events to build custom business metrics (e.g., the waiting time from the end of the initial screen rendering to the first important data update). ### [Marking Lynx Pipeline](/guide/performance/monitor-performance/timing-flag.md) - **Positioning**: A unique identifier used to mark a rendering pipeline for monitoring. It triggers the generation of a `PipelineEntry` and can also trigger a `MetricActualFmpEntry`. - **Usage**: - The recommended method is to set the `__lynx_timing_flag` attribute on a UI element. - Setting the value to `__lynx_timing_actual_fmp` additionally generates an ActualFMP metric event. - **Behavior and Limitations**: - The same Timing Flag is only effective the first time it is encountered (repeated markings will not trigger the callback again). - It is ineffective on tags that do not have a UI node, such as ``, ``, and `