前端【免费下载链接】famousThis repo is being deprecated. Please check out http://github.com/famous/engine项目地址https://gitcode.com/gh_mirrors/fa/famous点击查看免费下载本篇技术指南围绕 Famo.us 的核心布局机制展开Transform负责描述节点的位置、朝向与形变Modifier负责将静态或动态的Transform与size、origin、align等布局属性接入渲染树Render Tree从而完成对齐、定尺寸乃至随时间变化的动态布局。读完本篇你将掌握用矩阵原语构建复杂变换、用Modifier链式编排布局、用size/align/origin三件套实现精准对齐以及通过推push与拉pull两种模式驱动布局动画的完整实战能力。布局模型Transforms 与 ModifiersFamo.us 中的布局由Transforms与Modifiers共同管理。Transforms封装了 CSS3 transform 规范定义了一个节点在页面中的位置、朝向与形变。Transform是一个静态对象代表某一时刻的“快照”。Modifier则封装了随时间变化的Transform同时捆绑了size、origin、align等布局属性用于实现对齐alignment与齐平justification。Modifier可以被直接挂载到 Famo.us 场景图Scene Graph中作用于其下层的所有节点。关于场景图Render Tree本身的组织方式可参考仓库中的 渲染树指南渲染树以Context为根中间可以串联/并联任意多个Modifier末端挂载Surface等 renderableModifier对下层节点的Transform采用矩阵复合、对opacity采用相乘其效果会层层叠加。本篇聚焦于布局的“数学语言”与“挂载方式”。Transforms布局的底层矩阵语言Transform与 CSS3 变换矩阵一一对应它将translation平移、rotation旋转、scale缩放、skew倾斜四种分量组合进一个16 元素数组4x4 矩阵与 WebKit 矩阵一致即列主序在源码中以行主序数组存放。核心实现位于 Transform.js。分量说明默认值translation[x,y,z][0,0,0]rotation[x,y,z][0,0,0]scale[x,y,z][1,1,1]skew[x,y,z][0,0,0]Transform类提供两类静态方法把复杂Transform拆解为上述分量以及从分量构建Transform。Transform 原语所有Transform都由下列原语构建而成矩阵展示为行主序视角Transform返回值矩阵分量说明Transform.identity[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]translation :[0,0,0]rotation :[0,0,0]scale :[1,1,1]skew :[0,0,0]恒等变换对布局无任何影响。Transform.translate(x,y,z)[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1]translation :[x,y,z]其余为默认值沿 x、y、z 轴平移。源码中z缺省时补 0见 translate。Transform.scale(x,y,z)[x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1]scale :[x,y,z]其余为默认值沿 x、y、z 轴相对原点缩放。源码中y缺省时取x、z缺省时取 1见 scale即Transform.scale(2)等价于等比放大两倍。Transform.rotateX(θ)[1, 0, 0, 0, 0, cos(θ), -sin(θ), 0, 0, sin(θ), cos(θ), 0, 0, 0, 0, 1]rotation :[θ,0,0]其余为默认值在 y/z 平面内顺时针旋转绕 x 轴θ单位为弧度。Transform.rotateY(θ)[cos(θ), 0, sin(θ), 0, 0, 1, 0, 0, -sin(θ), 0, cos(θ), 0, 0, 0, 0, 1]rotation :[0,θ,0]其余为默认值在 x/z 平面内顺时针旋转绕 y 轴。Transform.rotateZ(θ)[cos(θ), -sin(θ), 0, 0, sin(θ), cos(θ), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]rotation :[0,0,θ]其余为默认值在 x/y 平面内顺时针旋转绕 z 轴是二维页面中最常用的旋转原语。源码实现见 rotateZ。Transform.skewX(θ)[1, 0, 0, 0, tan(θ), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]skew :[θ,0,0]其余为默认值沿 x 轴倾斜θ为顶边与左边之间的夹角。Transform.skewY(θ)[1, tan(θ), 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]skew :[0,θ,0]其余为默认值沿 y 轴倾斜θ为顶边与右边之间的夹角。Transform.inFront[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0.001, 1]translation :[0,0,0.001]其余为默认值向观察者方向做极小的 z 平移常用于层级layering控制。源码对应常量见 inFront。Transform.behind[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -0.001, 1]translation :[0,0,-0.001]其余为默认值远离观察者方向做极小的 z 平移常用于层级控制。源码对应常量见 behind。实际调用时旋转角度均使用弧度制例如旋转 45° 需写作Transform.rotateZ(Math.PI/4)可对照仓库示例 examples/core/Transform/example.js。构建复杂 Transform复杂Transform可以通过两种途径由原语构建在场景图中嵌套带有Transform的Modifier渲染树复合见 渲染树指南组合Transform本身。本篇聚焦后者。Transform的组合通过.multiply方法完成。尽管.multiply足以组合任意变换但对“在某个旋转上叠加平移”这类简单组合而言仍偏重Famo.us 为此提供了专门的优化方法方法说明Transform.multiply(T1,T2)等价于在T2之上叠加应用T1。注意顺序至关重要底层为假设末行是[0,0,0,1]的快速矩阵乘法见 multiply另有通用的multiply4x4。Transform.thenMove(T,p)将T再平移p [x,y,z]等价于Transform.multiply(Transform.translate(x,y,z), T)。实现上直接改写矩阵平移分量比乘法更快见 thenMove。Transform.moveThen(p,T)先平移p [x,y,z]再应用T等价于Transform.multiply(T, Transform.translate(x,y,z))见 moveThen。Transform.thenScale(T,s)将T再按s [x,y,z]缩放等价于Transform.multiply(Transform.scale(x,y,z), T)见 thenScale。Transform.aboutOrigin(T,p)将T的旋转/缩放原点移动到新点p [x,y,z]相对左上角的像素偏移。例如Transform.aboutOrigin(Transform.rotateZ(Math.PI/4), [50,100,0])将绕[50,100,0]这个枢轴点旋转而非左上角。实现见 aboutOrigin。Transform.average(T1,T2,w)通过分别平均两变换的rotate、translate、scale、skew分量返回带权重w的加权平均结果公式为(1-w)*T1 w*T2见 average。“先平移再旋转”与“先旋转再平移”结果不同这一点在仓库示例 examples/core/Modifier/chaining.js 中有直观演示两块 200x200 的 Surface 分别采用translate(200,0,-1)后再rotateZ(0.7)、以及rotateZ(0.7)后再translate(200,0,0)最终落位截然不同。拆解 Transform复杂Transform可以借助以下方法拆解为独立分量方法说明Transform.getTransform(T)返回T的translate分量即[t_x, t_y, t_z]。注意当前仓库源码中的实际方法名为Transform.getTranslate(m)实现即取矩阵元素m[12]、m[13]、m[14]见 getTranslate。Transform.interpret(T)返回一个包含translate、rotate、scale、skew四个键的对象。注意这是相对昂贵的操作非必要不建议使用。源码中interpret通过 Householder 反射进行 QR 分解来分离旋转与缩放并带有奇异矩阵保护与“双旋转”修正逻辑见 interpret。Transform.average内部正是依赖interpret完成分量平均后再用Transform.build重建矩阵的可见拆解主要用于插值、平均这类需要“分量级”操作的场景。Modifiers把 Transform 接入场景图Transform本身不能直接加入场景图。要把某个Transform施加到 renderable 上必须借助Modifier。Modifier是一个节点可以被直接挂到场景图上。可以把它理解成一个“壳”它接收Transform、opacity、对齐等原语并作用于其下层的所有节点这些节点可以是其他Modifier。下面演示把一个平移Transform施加到Surface上使其在 x、y 方向各偏移 100pxvar surface new Surface({ size: [50, 50], properties: { background: red } }); var modifier new Modifier({ transform : Transform.translate(100, 100, 0) }); context.add(modifier).add(surface);任何Transform都可以用这种方式施加。从源码看Modifier 构造函数 支持transform、opacity、origin、align、size、proportions六种选项每个选项最终都落到对应的xxxFrom(getter)方法上。而在渲染层面引擎每帧调用modify(target)内部的_update会逐一拉取各 getter 的当前值更新输出渲染规范render spec后再返回给场景图见 Modifier.modify 与 _update。这就是“静态属性 动态拉取”同时成立的根本原因transform选项传入的既可以是静态矩阵也可以是函数或带.get的对象。在进入动态布局之前需要先理解 Famo.us 的三个尺寸/对齐原语size、origin与align。对齐与尺寸size、align、origin除了定位Modifier还可以让 renderable 相对某个尺寸上下文size context对齐实现“居中”“左对齐”等效果。三个概念分工如下size定义内容的包围盒bounding-boxalign定义起点在包围盒中的位置比例值origin定义 renderable 自身的参考点比例值。SizeSize为内容定义包围盒。场景图中位于带size的Modifier之下的节点可以使用这个包围盒尺寸来定义其容器大小。最简单的例子是size [undefined, undefined]的Surface会继承父级Modifier的尺寸。下面例子中创建的Surface最终尺寸是[200, 100]尽管它的宽度最初被设为undefinedvar sizeModifier new Modifier({size: [200, 200]}); var surface new Surface({ size: [undefined, 100], properties: { background : red } }); context.add(sizeModifier).add(surface);这一概念不止适用于Surface也适用于任意 Famo.us renderable比如View。它对负责布局的View如GridLayout、SequentialLayout尤为重要这类布局对包围盒没有内建概念必须从父级Modifier继承size。相关实现可参考仓库中的 GridLayout.js 与 SequentialLayout.js。Align布局通常用“左上”“右下”等方位描述。Align定义的是相对size给出的包围盒的起始位置。Align用数组[x, y]表示比例每个分量取值在 0 到 1 之间。align的默认值是左上角即[0, 0]。常见对齐值如下Align Values含义[0, 0]左上[0.5, 0]顶部居中[1, 0]右上[0.5, 0.5]正中心[1, 0.5]右侧居中[0.5, 1]底部居中[1, 1]右下下面例子中align把Surface的左上角放到一个[100, 100]包围盒的“左侧垂直居中”位置//////////////////////////////////// // // 100 x 100 bounding box // ↙ // ┌──────────────┐ // │ │ // │ │ // ├──────┐ │ // │ │ │ // ├──────┘ │ // └──────────────┘ // //////////////////////////////////// var surface new Surface({ size: [50, 30], properties: { background: red } }); var sizeModifier new Modifier({ size: [100, 100] }); var alignModifier new Modifier({ align: [0, 0.5] }); context.add(sizeModifier).add(alignModifier).add(surface);仓库示例 examples/core/Modifier/align.js 展示了更完整的用法它在一个 300x300 的尺寸上下文中放了一张带边框的Surface并在其上用一个align可变的Modifier承载一块 100x100 的Surface点击页面即按九宫格循环切换[0,0]、[.5,0]、[1,0]等九个对齐位置。其中align甚至可以直接传一个返回数组的函数印证了上文“getter 可以是函数”的实现事实。Origin上面我们对齐的是Surface的左上角。如果要按它的中心对齐怎么办设置Modifier的origin属性即可。关键区别在于align相对于父级尺寸origin相对于renderable 自身。二者组合的效果是把 renderable 的origin点放到父级align点所在的位置。图示如下align point () origin point (○) ┌─────────────────┐ ┌───────┐ │ │ │ │ │ │ │ ○ │ │ │ └───────┘ │ │ (renderable) │ │ └─────────────────┘ (bounding box) align origin (⊕) ┌─────────────────┐ │ ┌───────┐ │ │ │ │ │ │ │ ⊕ │ │ │ └───────┘ │ │ │ └─────────────────┘ (renderable inside bounding box)例如要把Surface在某个尺寸上下文内居中就需要把Surface的中心放到该上下文中心。代码写法//////////////////////////////// // // 100 x 100 bounding box // ↙ // ┌────────────┐ // │ ┌──────┐ │ // │ │ │ │ // │ │ │ │ // │ └──────┘ │ // └────────────┘ // //////////////////////////////// var surface new Surface({ size: [70, 70], properties: { background: red } }); var sizeModifier new Modifier({ size: [100, 100] }); var centerModifier new Modifier({ align: [0.5, 0.5], origin: [0.5, 0.5] }); context .add(sizeModifier) .add(centerModifier) .add(surface);origin的默认值同样是左上角[0, 0]。使用要点sizeModifier必须位于alignModifier之前因为align必须引用一个父级尺寸这两个Modifier不能合并成一个——承担“定义包围盒”与“在包围盒内对齐”职责的节点必须分离。动态布局让布局属性随时间动画以上我们只把transform、size、origin、align当作Modifier的静态属性。实际上这些属性每一个都可以随时间动画有两种实现路径推式动画push-based拉式动画pull-based推式动画Push-basedStateModifier 是推式实现的代表。所谓“推”是指状态变化由StateModifier内部维护并主动向下推进。从源码看StateModifier 构造函数 内部为每种属性都创建了一个TransitionableTransitionableTransform管transformTransitionable管opacity、origin、align、size、proportions并把这些可过渡对象作为 getter 注入内部持有的一个普通Modifier调用setXxx时真正发生的是“向对应的Transitionable推送目标值与过渡定义”。StateModifier提供如下方法方法说明StateModifier.setTransform(T, definition, callback)设置transform状态。StateModifier.setOpacity(opacity, definition, callback)设置opacity状态。StateModifier.setSize(size, definition, callback)设置size状态。StateModifier.setOrigin(origin, definition, callback)设置origin状态。StateModifier.setAlign(align, definition, callback)设置align状态。一个推式动画 renderable 的例子把不透明度从 1 线性过渡到 0耗时 500msvar surface new Surface({ size: [70, 70], properties: { background: red } }); var stateModifier new StateModifier({ opacity: 1 }); context.add(stateModifier).add(surface); // animate the opacity from 1 to 0 over 500ms using a linear easing curve stateModifier.setOpacity( 0, {curve: linear, duration : 500}, function() { console.log(animation finished!) } );过渡定义definition形如{curve: 曲线名或函数, duration: 毫秒数}若省略过渡定义则状态瞬时生效见 StateModifier.setTransform 等 set 方法的实现注释。仓库示例 examples/modifiers/StateModifier/example.js 展示了实战用法点击页面时先halt()中断上一轮动画再setTransform(Transform.rotateZ(Math.random()*Math.PI/2), { curve: easeOut, duration: 5000 })随机旋转。halt()会冻结所有六个Transitionable的当前状态并清除未完成的动作见 StateModifier.halt。拉式动画Pull-basedModifier 类是拉式实现的代表。Modifier自身不保存任何状态比如当前Transform是什么状态由外部通过getter提供——getter 可以是一个返回状态值的函数或者一个带.get方法的对象如Transitionable。Modifier提供如下方法方法说明Modifier.transformFrom(transformGetter)从transformGetter拉取transform状态。Modifier.opacityFrom(opacityGetter)从opacityGetter拉取opacity状态。Modifier.sizeFrom(sizeGetter)从sizeGetter拉取size状态。Modifier.originFrom(originGetter)从originGetter拉取origin状态。Modifier.alignFrom(alignGetter)从alignGetter拉取align状态。源码 transformFrom 等方法的实现清晰体现了三种输入的分流函数直接作为 getter 保存带.get的对象绑定其get作为 getter其他静态值则直接写入输出渲染规范。每次渲染 tick_update会调用所有已注册的 getter 刷新输出见 Modifier._update。上面的透明度动画可以用拉式方式重写如下var surface new Surface({ size: [70, 70], properties: { background: red } }); var modifier new Modifier(); context.add(modifier).add(surface); // the opacityGetter var opacityState new Transitionable(1); modifier.opacityFrom(opacityState); // animate the opacity from 1 to 0 over 500ms using a linear easing curve opacityState.set( 0, {curve : linear, duration : 500}, function(){ console.log(animation finished!); } );这里动画的真正驱动力来自Transitionable。它是一个数值状态维护器构造函数设置初始状态set(endState, transition, callback)把“目标状态 过渡定义 完成回调”压入动作队列并启动过渡get()返回当前插值状态见 Transitionable.set 与 Transitionable.get。Transitionable底层使用TweenTransition默认或注册的其他过渡引擎支持{duration, curve}的过渡定义并可通过delay、halt、reset等 API 精细控制节奏。它天然就是“带.get方法的对象”因此能无缝充当各种xxxFrom的 getter。仓库示例 examples/transitions/Transitionable/example.js 给出了更完整的拉式用法一个Transitionable(-100)作为 x 位移的驱动器通过modifier.transformFrom(function() { return Transform.translate(transitionable.get(), 0, 0); })传入函数型 getter点击 Surface 后transitionable.set(100, {curve: easeInOut, duration: 1000})从而把“位移动画”解耦到渲染树之外。两种模式的取舍维度推式StateModifier拉式Modifier getter状态归属状态保存在StateModifier内部各Transitionable中状态保存在外部 getter函数或Transitionable中调用方式setTransform/setOpacity/setSize/setOrigin/setAligntransformFrom/opacityFrom/sizeFrom/originFrom/alignFrom组合灵活性状态封装在内部调用简洁状态可复用、可在多个Modifier间共享同一Transitionable典型场景单节点快速声明式动画需要把动画状态与布局逻辑解耦、或状态被多处复用时需要注意普通Modifier上同样存在setTransform、setOpacity等“遗留”方法它们会在首次调用时惰性创建内部Transitionable并回退到推式逻辑——源码明确将这些方法标注为deprecated并建议改用transformFrom/Transitionable见 Modifier.setTransform 的注释。新代码应优先采用本篇介绍的规范写法。延伸阅读渲染树指南理解Context、Modifier、Surface、View如何组织成场景图以及Modifier复合与分支的语义。Transform 实现源码全部矩阵原语、组合与拆解方法。Modifier 实现源码静态属性与拉式 getter 的完整实现。StateModifier 实现源码推式动画的内部状态组织。Transitionable 实现源码动画驱动器的队列、插值与回调机制。配套可运行示例examples/core/Transform/example.js、examples/core/Modifier/align.js、examples/core/Modifier/chaining.js、examples/modifiers/StateModifier/example.js、examples/transitions/Transitionable/example.js。赞分享前端【免费下载链接】famousThis repo is being deprecated. Please check out http://github.com/famous/engine项目地址https://gitcode.com/gh_mirrors/fa/famous点击查看免费下载相关推荐X6 树布局实战基于 antv/hierarchy 的脑图树渲染指南X6 树布局实战基于 antv/hierarchy 的脑图树渲染指南 本文以 X6 仓库中 site/examples/layout/tree https:前端图形学Fresh 布局系统完全指南基于 _layout.tsx 的共享 UI、嵌套渲染与布局继承控制Fresh 布局系统完全指南基于 _layout.tsx 的共享 UI、嵌套渲染与布局继承控制 Fresh 的布局Layout机制允许你通过文件系统约定后端前端Mermaid Treemap 树形图语法、配置项与基于 D3 的布局渲染实现详解Mermaid Treemap 树形图语法、配置项与基于 D3 的布局渲染实现详解 本篇基于 Mermaid 官方 treemap 语法文档 docs/sy图表库前端数据可视化上一篇把模糊老视频变清晰Video2X 视频放大与插帧上手指南下一篇【亲测免费】 探索 BeautifulReport一份优雅的 Python 报告生成工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考