diff --git a/.agents/skills/mpx2rn/references/rn-style-reference.md b/.agents/skills/mpx2rn/references/rn-style-reference.md index dde5a37981..6c2c189261 100644 --- a/.agents/skills/mpx2rn/references/rn-style-reference.md +++ b/.agents/skills/mpx2rn/references/rn-style-reference.md @@ -294,7 +294,9 @@ RN 仅原生支持部分 CSS 简写属性,Mpx 分别在**编译时**和**运 - **过渡简写**:`transition` - 在运行时将 `transition` 字符串简写解析为动画配置,如 `transition: opacity 0.3s ease` → 解析出 `property`、`duration`、`timingFunction` 等参数 - - 仅 `view` 组件支持;`transition-property` 不支持 `all`,需显式指定属性名;不支持 `step-start`、`step-end`、`steps()` 等阶梯时间函数 + - 仅 `view` 组件支持;`transition-property` 不支持 `all`,需显式指定属性名,且属性列表在组件生命周期内必须保持稳定 + - 支持动态更新 `transition-duration`、`transition-delay`、`transition-timing-function`;也可动态更新 `transition` 简写中的对应值,但不能动态添加、移除或替换其中的 `transition-property` + - 不支持 `step-start`、`step-end`、`steps()` 等阶梯时间函数 - **间距多值语法**:`margin`、`padding` 的 2-4 值语法 - 如 `margin: 10px 20px` → `marginTop: 10, marginRight: 20, marginBottom: 10, marginLeft: 20` - 单值语法 RN 原生支持,运行时不展开 @@ -420,23 +422,38 @@ Mpx 在 RN 平台支持以下动画方式: - 仅 `view` 组件支持动画相关属性。 -**自动检测与 enable-animation:** 框架会自动检测样式和属性中的动画定义来确定动画类型(如检测到 `transition` 样式或 `animation` 属性)。如果样式中一开始不存在动画定义,但后续需要动态添加(如通过 `wx:style` 动态添加 `transition`),建议使用 `enable-animation` 属性预先声明动画类型。 +**自动检测与 enable-animation:** 框架会在首次渲染时根据样式和属性中的动画定义确定动画类型(如检测到 `transition` 样式或 `animation` 属性),动画类型在组件生命周期内必须保持稳定。也可通过 `enable-animation="api"` / `enable-animation="transition"` 显式指定使用 Animation API / CSS Transition;布尔值 `true` 等价于 `api`,不能用于预声明 CSS Transition。如果首次渲染时只有 `transition-property`,时长等参数后续才通过 `wx:style` 动态设置,需保证 `transition-property` 从首次渲染起保持稳定。 ```html ``` +也可以动态更新 `transition` 简写,但其中的属性列表必须与首次渲染保持一致: + +```js +this.dynamicStyle = { + transition: this.enableTransition ? 'transform 0.5s ease-out 0.1s' : 'transform 0s linear 0s', + transform: this.shouldDisplay ? 'translateX(100px)' : 'translateX(0)' +} +``` + ### 背景图支持 Mpx 在 RN 平台支持 CSS 背景图及渐变背景,框架会自动处理样式转换。 @@ -600,10 +617,10 @@ Mpx 在 RN 平台支持 CSS 背景图及渐变背景,框架会自动处理样 | 属性 | 值类型 | 说明 | 示例 | | --- | --- | --- | --- | | `transition` | `property duration timing-function delay` | 过渡简写 | `transition: opacity 0.3s ease`;`transition: width 0.5s ease-in-out 0.1s` | -| `transition-property` | `string` | 过渡属性(不支持 `all`) | `transition-property: opacity, width` 指定多个过渡属性 | -| `transition-duration` | `time` | 过渡持续时间 | `transition-duration: 0.3s`;`transition-duration: 300ms` | -| `transition-delay` | `time` | 过渡延迟时间 | `transition-delay: 0.1s` 延迟 0.1 秒后开始过渡 | -| `transition-timing-function` | `linear` \| `ease` \| `ease-in` \| `ease-out` \| `ease-in-out` \| `cubic-bezier` | 过渡时间函数 | `transition-timing-function: ease-in-out`;`transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)` | +| `transition-property` | `string` | 过渡属性(不支持 `all`);属性列表在组件生命周期内必须保持稳定,不支持动态添加、移除或替换 | `transition-property: opacity, width` 指定多个过渡属性 | +| `transition-duration` | `time` | 过渡持续时间,支持动态更新 | `transition-duration: 0.3s`;`transition-duration: 300ms`;可设为 `0s` 临时关闭过渡 | +| `transition-delay` | `time` | 过渡延迟时间,支持动态更新 | `transition-delay: 0.1s` 延迟 0.1 秒后开始过渡 | +| `transition-timing-function` | `linear` \| `ease` \| `ease-in` \| `ease-out` \| `ease-in-out` \| `cubic-bezier` | 过渡时间函数,支持动态更新 | `transition-timing-function: ease-in-out`;`transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)` | ### 其他属性 diff --git a/.agents/skills/mpx2rn/references/rn-template-reference.md b/.agents/skills/mpx2rn/references/rn-template-reference.md index 509787cb90..fbe6d7d179 100644 --- a/.agents/skills/mpx2rn/references/rn-template-reference.md +++ b/.agents/skills/mpx2rn/references/rn-template-reference.md @@ -699,7 +699,7 @@ Mpx 输出 RN 内置支持了大部分常用的基础组件,详情见下方文 | hover-stay-time | number | `400` | 手指松开后点击态保留时间,单位毫秒 | | animation | object | | 传递动画的实例, 可配合 mpx.createAnimation 方法一起使用 | | enable-background | boolean | `false ` | RN 环境特有属性,是否要开启 background-image、background-size 和 background-position 的相关计算或渲染,请根据实际情况开启 | -| enable-animation | boolean | `false` | RN 环境特有属性,开启要开启动画渲染,请根据实际情况开启 | +| enable-animation | boolean \| `api` \| `transition` | `false` | RN 环境特有属性,显式指定 Animation API 或 CSS Transition 动画类型;`true` 等价于 `api`;首次根据渲染的 `animation` 属性或 transition 样式自动检测;| | enable-fast-image | boolean | `false` | RN 环境特有属性,开启后将使用 react-native-fast-image 进行图片渲染,请根据实际情况开启 | | is-simple | - | - | RN 环境特有标记,设置后将使用简单版本的 view 组件渲染,该组件不包含 css var、calc、ref 等拓展功能,但性能更优,请根据实际情况设置 | @@ -715,7 +715,8 @@ Mpx 输出 RN 内置支持了大部分常用的基础组件,详情见下方文 - 如果从未使用背景图、动图或动画,请不要开启`enable-background`、`enable-animation`或`enable-fast-image`属性,会有一定的性能消耗。 - 若开启`enable-background`需要给当前 view 组件设置一个唯一 key。 - `background-image`、`background-size`、`background-position` 等背景图相关 css 属性,仅 view 组件支持 -- 出于性能考虑,view 的样式增强能力(如 `enable-background`、`enable-animation`)采用按需启用策略。view 组件仅在**首次**渲染时检测样式并决定是否开启对应能力。由于 React Hooks 的一致性约束,增强能力无法在后续更新阶段再动态启用,因此当组件生命周期内**可能**使用相关能力时,需在首次渲染时**显式声明**启用,比如 `enable-animation="{{ true }}"`。 +- 出于性能考虑,view 的样式增强能力(如 `enable-background`、`enable-animation`)采用按需启用策略。view 组件仅在**首次**渲染时检测样式并决定是否开启对应能力。由于 React Hooks 的一致性约束,增强能力无法在后续更新阶段再动态启用,因此当组件生命周期内**可能**使用相关能力时,需在首次渲染时**显式声明**启用,比如 `enable-animation="{{ 'transition' }}"`。 +- CSS Transition 的 `transition-property` 在组件生命周期内必须保持稳定,不支持动态添加、移除或替换;`transition-duration`、`transition-delay`、`transition-timing-function` 支持动态更新。 ### text diff --git a/docs-vitepress/guide/rn/component.md b/docs-vitepress/guide/rn/component.md index 6a56a3f781..9b7057a8df 100644 --- a/docs-vitepress/guide/rn/component.md +++ b/docs-vitepress/guide/rn/component.md @@ -49,16 +49,16 @@ 视图容器。 属性 -| 属性名 | 类型 | 默认值 | 说明 | -| ----------------------- | ------- | ------------- | ---------------------------------------------------------- | -| hover-class | string | | 指定按下去的样式类。 | -| hover-start-time | number | `50` | 按住后多久出现点击态,单位毫秒| -| hover-stay-time | number | `400` | 手指松开后点击态保留时间,单位毫秒 | -| animation | object | | 传递动画的实例, 可配合mpx.createAnimation方法一起使用| -| enable-background | boolean | `false ` | RN环境特有属性,是否要开启background-image、background-size和background-position的相关计算或渲染,请根据实际情况开启 | -| enable-animation | boolean | `false` | RN环境特有属性,开启要开启动画渲染,请根据实际情况开启 | -| enable-fast-image | boolean | `false` | RN环境特有属性,开启后将使用 react-native-fast-image 进行图片渲染,请根据实际情况开启 | -| is-simple | - | - | RN环境特有标记,设置后将使用简单版本的 view 组件渲染,该组件不包含 css var、calc、ref 等拓展功能,但性能更优,请根据实际情况设置 | +| 属性名 | 类型 | 默认值 | 说明 | +| ----------------------- |---------|------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| +| hover-class | string | | 指定按下去的样式类。 | +| hover-start-time | number | `50` | 按住后多久出现点击态,单位毫秒 | +| hover-stay-time | number | `400` | 手指松开后点击态保留时间,单位毫秒 | +| animation | object | | 传递动画的实例, 可配合mpx.createAnimation方法一起使用 | +| enable-background | boolean | `false ` | RN环境特有属性,是否要开启background-image、background-size和background-position的相关计算或渲染,请根据实际情况开启 | +| enable-animation | boolean\|enum(`api`/`transition`) | `false` | RN 环境特有属性,显式指定 Animation API 或 CSS Transition 动画类型;`true` 等价于 `api`;首次根据渲染的 `animation` 属性或 `transition` 样式自动检测;| +| enable-fast-image | boolean | `false` | RN环境特有属性,开启后将使用 react-native-fast-image 进行图片渲染,请根据实际情况开启 | +| is-simple | - | - | RN环境特有标记,设置后将使用简单版本的 view 组件渲染,该组件不包含 css var、calc、ref 等拓展功能,但性能更优,请根据实际情况设置 | 事件 diff --git a/docs-vitepress/guide/rn/style.md b/docs-vitepress/guide/rn/style.md index 13cd025226..3557f38f4c 100644 --- a/docs-vitepress/guide/rn/style.md +++ b/docs-vitepress/guide/rn/style.md @@ -1581,15 +1581,19 @@ object-fit: scale-down; /* 缩小显示 */ ## 跨端动画 {#cross-platform-animation} + 基础组件 view 支持两种动画形式 createAnimation API 和 transition, 可以通过设置 animation 属于来使用 createAnimation API 动画,通过 class 或者 style 设置 css transition 来使用 transition 动画, 可以用过 prop enable-animation = api/transition 来指定使用 createAnimation API/transition 的动画形式,,enable-animation 设置 true 默认为 createAnimation API 形式,需要注意的是指定动画类型后,对应的动画参数也需要匹配设置,详细使用文档如下: ### createAnimation 动画API {#create-animation-api} + 创建一个动画实例 animation,调用实例的方法来描述动画,最后通过动画实例的 export 方法导出动画数据传递给组件的 animation 属性。 详情参考以下动画部分微信小程序文档,以下仅描述支持能力有差异部分: + #### [wx.createAnimation](https://developers.weixin.qq.com/miniprogram/dev/api/ui/animation/wx.createAnimation.html) - 参数 timingFunction 不支持 step-start 和 step-end + #### [动画实例 animation](https://developers.weixin.qq.com/miniprogram/dev/api/ui/animation/Animation.html) {#animation-instance} - translateZ() 不支持 - translate3d() 不支持 @@ -1601,8 +1605,22 @@ object-fit: scale-down; /* 缩小显示 */ - animation.matrix3d() 不支持 ### CSS transition -CSS transition 动画至少需要设置动画时长和动画属性,可通过单独属性 transition-property 和 transition-property 设置,也可以通过 transition 缩写设置 ->重要提示:transition 支持设置百分比,如 ```marginTop: 1%;marginTop: 100%;```;要注意的是起始值和结束值需设置为同一类型,同为px或者同为百分比, 支持 ```marginTop: 10px;marginTop: 100px; ```,**不支持 ```marginTop: 10px; marginTop: 100%;```** + +CSS transition 动画至少需要设置动画时长和动画属性,可通过单独属性 `transition-property` 和 `transition-duration` 设置,也可以通过 transition 缩写设置 + +> 重要提示: +> `transition-property` 需要保持稳定,不支持通过条件控制添加和移除 `transition-property` +> `transition` 支持设置百分比,如 ```marginTop: 1%;marginTop: 100%;```;要注意的是起始值和结束值需设置为同一类型,同为px或者同为百分比, 支持 ```marginTop: 10px;marginTop: 100px; ```,**不支持 ```marginTop: 10px; marginTop: 100%;```** + +```js +// ❌ Bad 不支持 transition-property transform 动态设置 +const dotTransition = computed(() => enableTransition.value ? '' : 'transition: transform 0.5s') + +// ✅ Good 保持 transition-property transform 稳定,通过动态控制 transform-duration 来控制动画 +const dotTransition = computed(() => enableTransition.value ? 'transition: transform 0s' : 'transition: transform 0.5s') +const dotTransition = computed(() => enableTransition.value ? 'transition-property:transform; transition-duration: 0;' : 'transition-property:transform; transition-duration: 0.5s;') +``` + #### [transition](https://developer.mozilla.org/en-US/docs/Web/CSS/transition) ```css @@ -1629,7 +1647,9 @@ transition: 2s, 1s; /**** 不支持:property 不支持设置为 all */ transition: all 0.5s ease-out ``` + #### [transition-property](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-property) + 不支持设置为 all,不支持自定义 > 支持的 property 合集有: > rotateX rotateY rotateZ scaleX scaleY skewX skewY translateX translateY opacity backgroundColor width height top right bottom left color borderColor borderBottomColor borderLeftColor borderRightColor borderTopColor borderTopLeftRadius borderTopRightRadius borderBottomLeftRadius borderBottomRightRadius borderRadius borderBottomWidth borderLeftWidth borderRightWidth borderTopWidth borderWidth margin marginBottom marginLeft marginRight marginTop maxHeight maxWidth minHeight minWidth padding paddingBottom paddingLeft paddingRight paddingTop @@ -1653,7 +1673,9 @@ transition-property: revert; transition-property: revert-layer; transition-property: unset; ``` + #### [transition-duration](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transition-duration) + ```css /**** 支持 */ transition-duration: 6s; @@ -1661,27 +1683,42 @@ transition-duration: 120ms; transition-duration: 1s, 15s; transition-duration: 10s, 30s, 230ms; ``` + #### [transition-delay](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transition-delay) + ```css /**** 支持 */ transition-delay: 3s; transition-delay: 2s, 4ms; ``` + #### [transition-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior) + 不支持 + #### [transition-timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) + 仅支持 ease、ease-in、ease-out、ease-in-out、linear、cubic-bezier(),不支持 step-start、step-end、steps() ### CSS animation + 暂不支持 ### 动画监听事件 {#animation-event-listener} + #### transitionend + - CSS transition 结束或 wx.createAnimation 结束一个阶段时触发 - 不属于冒泡事件,需要绑定在真正发生了动画的节点上才会生效 + #### animationstart + 暂不支持 + #### animationiteration + 暂不支持 + #### animationend + 暂不支持 diff --git a/packages/webpack-plugin/lib/runtime/components/react/animationHooks/index.ts b/packages/webpack-plugin/lib/runtime/components/react/animationHooks/index.ts index 90d3fb6f23..3d1c09ed75 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/animationHooks/index.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/animationHooks/index.ts @@ -13,7 +13,7 @@ export default function useAnimationHooks (props: _ViewProps & { enableAni const { style: originalStyle = {}, enableAnimation, animation, bindtransitionend, layoutRef } = props // 记录动画类型 let animationType = '' - if (hasOwn(originalStyle, 'animation') || (hasOwn(originalStyle, 'animationName') && hasOwn(originalStyle, 'animationDuration'))) { + if (hasOwn(originalStyle, 'animation') || hasOwn(originalStyle, 'animationName')) { // css animation 只做检测提示 animationType = 'animation' } @@ -21,7 +21,7 @@ export default function useAnimationHooks (props: _ViewProps & { enableAni animationType = 'api' } // 优先级 css transition > API - if (hasOwn(originalStyle, 'transition') || (hasOwn(originalStyle, 'transitionProperty') && hasOwn(originalStyle, 'transitionDuration'))) { + if (hasOwn(originalStyle, 'transition') || hasOwn(originalStyle, 'transitionProperty')) { animationType = 'transition' } // 优先以 enableAnimation 定义类型为准 diff --git a/packages/webpack-plugin/lib/runtime/components/react/animationHooks/useTransitionHooks.ts b/packages/webpack-plugin/lib/runtime/components/react/animationHooks/useTransitionHooks.ts index 75f8014f08..244ac56ae2 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/animationHooks/useTransitionHooks.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/animationHooks/useTransitionHooks.ts @@ -144,7 +144,7 @@ function parseTransitionStyle (originalStyle: ExtendedViewStyle) { const transitionMap = transitionData.reduce((acc, cur) => { // hasOwn(transitionSupportedProperty, dash2hump(val)) || val === Transform const { property = '', duration = 0, delay = 0, easing = Easing.inOut(Easing.ease) } = cur - if ((hasOwn(transitionSupportedProperty, dash2hump(property)) || property === 'transform') && duration > 0) { + if ((hasOwn(transitionSupportedProperty, dash2hump(property)) || property === 'transform') && duration >= 0) { acc[property] = { duration, delay, @@ -157,17 +157,51 @@ function parseTransitionStyle (originalStyle: ExtendedViewStyle) { return transitionMap } +const transitionKeys = ['transition', 'transitionDuration', 'transitionTimingFunction', 'transitionDelay', 'transitionProperty'] as const + +function getTransitionPropertyKeys (map: TransitionMap): string { + return Object.keys(map).sort().join(',') +} + export default function useTransitionHooks (props: AnimationHooksPropsType) { // console.log(`useTransitionHooks, props=`, props) const { style: originalStyle = {}, transitionend } = props // style变更标识(首次render不执行),初始值为0,首次渲染后为1 const animationDeps = useRef(0) - // 记录上次style map - // const lastStyleRef = useRef({} as {[propName: keyof ExtendedViewStyle]: number|string}) - // ** 从 style 中获取动画数据 + // transition 时序属性动态更新追踪 + const lastTransitionStyleRef = useRef>( + transitionKeys.reduce((acc, key) => { acc[key] = originalStyle[key]; return acc }, {} as Record) + ) + const prevTransitionMapRef = useRef(null) + // ** 从 style 中获取动画数据(支持动态更新 transitionDuration/transitionDelay/transitionTimingFunction) const transitionMap = useMemo(() => { - return parseTransitionStyle(originalStyle) - }, []) + const prevStyle = lastTransitionStyleRef.current + // 检测 transition 时序属性是否变化并更新 + let hasChanged = false + transitionKeys.forEach(key => { + if (prevStyle[key] !== originalStyle[key]) { + hasChanged = true + prevStyle[key] = originalStyle[key] + } + }) + // 时序属性未变化且非首次计算,跳过重新解析 + if (!hasChanged && prevTransitionMapRef.current) { + return prevTransitionMapRef.current + } + const newTransitionMap = parseTransitionStyle(originalStyle) + if (!prevTransitionMapRef.current) { + // 首次计算 + prevTransitionMapRef.current = newTransitionMap + return newTransitionMap + } + // 检测 transitionProperty 是否变化,变化时直接返回上一次的 transitionMap + if (getTransitionPropertyKeys(newTransitionMap) !== getTransitionPropertyKeys(prevTransitionMapRef.current)) { + error('[Mpx runtime error]: dynamic setting transitionProperty is not supported') + return prevTransitionMapRef.current + } + prevTransitionMapRef.current = newTransitionMap + return newTransitionMap + }, [originalStyle]) // ** style prop sharedValue interpolateOutput: SharedValue const { shareValMap, animatedKeys, animatedStyleKeys } = useMemo(() => { // 记录需要执行动画的 propName