A-Frame ECS and Three.js Transform Authority: Which Should Be the Source of Truth During Animation?
0 reputation · 23 Dec 2023, 14:47 UTC
0 reputation · 23 Dec 2023, 14:47 UTC
I'm designing a custom A-Frame component that drives a complex animation on an entity, and I need to decide where the authoritative transform state lives: the HTML attributes on the <a-entity> element, or the underlying Three.js Object3D matrix that A-Frame wraps.
The component updates position and rotation every frame in its tick function. I know A-Frame maps attribute changes to Three.js properties through the set and update lifecycle methods, and that directly mutating the Three.js object outside the component lifecycle risks desynchronizing the scene graph from the DOM. Other components on the same entity (and potentially third-party ones) may also read or write the transform, so whichever authority I choose has to stay consistent for them.
Writing back to attributes every frame seems expensive given the DOM overhead per entity, but writing only to the Three.js object means getAttribute('position') may return stale values for anything else inspecting the entity.
tick, is mutating el.object3D directly the documented and supported pattern, or should updates go through el.setAttribute?object3D directly, is there a supported way to keep attribute reads consistent for other components without paying the DOM write cost each frame?31275 reputation · 23 Dec 2023, 17:24 UTC
For per‑frame animation in tick, mutating el.object3D directly is a documented, supported pattern, but it is *not* the single source of truth for the framework. The authoritative state for the entity’s transform remains the DOM attributes (or the component data that those attributes drive). Updating the Three.js matrix alone does not propagate changes back to the attribute system; other components that read el.getAttribute('position') will see stale values unless you manually sync them.
setAttribute → update) to react to changes.getAttribute for positioning.object3D mutations bypass the component lifecycle, breaking those expectations.setAttribute or the component’s data property. Example:
tick: function (t, dt) {
const pos = this.el.getAttribute('position');
pos.x += 0.01 * dt;
this.el.setAttribute('position', pos);
}
object3D matrix internally.object3D Directly – When and Howobject3D, manually sync the attribute if needed:
this.el.object3D.position.set(1,2,3);
this.el.setAttribute('position', '1 2 3');
el._cachedPosition) and expose a getter for other components to use. This avoids DOM writes but bypasses the normal lifecycle.All releases from 1.0.0 to 1.5.x maintain the same attribute‑to‑object3D sync logic. No version‑specific changes affect the decision; the framework still treats attributes as the source of truth.
const el = document.querySelector('a-entity');el.object3D.position.set(5, 5, 5);console.log(el.getAttribute('position')); // still old valueel.setAttribute('position', '5 5 5');el.getAttribute('position') matches the matrix.If other components on the entity rely on getAttribute('position') for logic (e.g., physics or collision), you must keep the attribute in sync. If not, direct object3D mutation with manual sync when necessary is acceptable.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.