A-Frame и angular 6: просмотр частей, вставленных в dom, но не в сцену - PullRequest
0 голосов
/ 06 июня 2018

Мотивация
У меня есть приложение Angular 6, в котором есть несколько сцен A-Frame.Я хотел бы внедрить в каждую сцену некоторые общие "partials" (в rails lingo), содержащие HTML-элементы A-Frame, такие как панель конфигурации afame-gui .Таким образом, я не дублирую элементы HTML в каждом представлении.Это стандартная вещь в ng6, и вы делаете это, создавая компонент ng для партиала, который затем получает свой собственный тег, который вы затем ссылаетесь в каждом родительском компоненте, с которым хотите смешать его:

   <a-scene>
      <app-config-panel></app-config-panel>  <-- insert partial #1
      <ng-template appDynamicLoad></ng-template> <-- insert partial #2
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>

У меня есть более полный пример здесь

Задача
К сожалению, в то время как Angular 6 действительно вводит частичное в DOMЯ не вижу этого в сцене A-Frame.

Я знаю, что в A-Frame есть специальные методы createElement , setAttribute и appendChild , которые переопределяют поведение по умолчанию и добавляют дополнительную информацию,например, THREE.object3D для каждого элемента.Предположительно, angular 6 не использует эти методы для вставки в DOM (обратите внимание, это относится только к частичным: «основной» компонент, содержащий сцену, работает должным образом).На самом деле, при проверке элементов DOM я do вижу Object3D, но в нем нет никакой информации о материале или геометрии.

Итак, мой вопрос: есть ли способ заставить Angular управлять этими методами и таким образом вставить в сцену A-Frame?

Подробности
Я нашел это , в котором упоминается способ заставить angular вызывать setAttribute и, таким образом, правильно вставить его в сцену в виде рамки, но мне не хватило информации, чтобы понять это.

Я попытался динамически загрузить компонент на основе этого сложного примера из угловых документов , но это то же самое: вставляет в DOM, но не в сцену.

Да, яМожно вручную создать и вставить элемент программно из моего сценария, что-то вроде этого:

createEl() {
    let newEl = this.renderer.createElement('a-entity');

    this.renderer.setAttribute(newEl, 'text', this.msg);
    this.renderer.setAttribute(newEl, 'position', this.position);

    return newEl;
  }

Но я бы предпочел сделать это, используя шаблон в официальном угловом формате.

Странно, что angular прекрасно работает для родительского компонента, но не для частичного компонента.

Боюсь, это просто несоответствие А-образного / углового сопротивления, и я просто буду вынужден сделать это вручную, но это никогда не повредит, верно?

Angular 6.0.3
A-Frame 0.8.2

Ответы [ 2 ]

0 голосов
/ 15 июня 2018

Используйте registerPrimitve A-кадра, чтобы заставить a-frame думать, что ng-частичное является "нативным" объектом a-frame.Это основано на втором решении моей предыдущей статьи, которое я на самом деле тестировал и проверял, и это гораздо лучшее и более простое решение.

Вам просто нужно вызвать что-то вроде следующего из файла .js длязарегистрируйте свои теги ng (в моем конкретном случае я решил разместить скрипт в "--ng-root - / init_scripts / register-ng-primitives.js '):

// Register all the ng-generated tags in your application so that a-frame
// recognizes them as one of it its own, and thus properly inserts them into the
// scene.  If ng tags are not registered with a-frame then ng will only insert
// them into to the DOM but *not* into the a-frame scene.
// A-frame's 'registerPrimitive' *must* be called in angular's polyfill.js after
// 'aframe.js' and before 'zone.js'.
//
AFRAME.registerPrimitive('app-sub-scene-a', { mappings: {} }); //<- specify all your ng tags starting here
AFRAME.registerPrimitive('app-sub-scene-b', { mappings: {} });

См.a-frame docs для более подробной информации о регистрации примитивов .

Единственное осложнение в угловой среде - , где это нужно сделать. Вам нужно сделать call registerPrimitve после загрузки aframe.js (так определено AFRAME ), но до вызова "zone.js".

Вы можете сделать это, настроив свой 'src / polyfill.js 'примерно так:

//import 'aframe'; <- alternate way to load aframe
import '../node_modules/aframe/dist/aframe-master'; //<- useful if you want to call a specific version of a-frame
import '../init_scripts/register-ng-primitives'; <- put your script here


/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js/dist/zone';  // Included with Angular CLI. <- need to call before this

Примечание: кажется, что polyfill.js не разрешает загрузку из-под' / src ', поэтому очевидные места для размещения вашего пользовательского скрипта находятся в'node_modules 'или в каком-то другом каталоге в ангулекореньЯ решил создать пользовательский каталог с именем 'init_scripts', потому что я считаю, что _node_modules_ должен быть зарезервирован для системного кода, плюс вы захотите добавить этот скрипт в репозиторий.

Если вы попытаетесь вызвать registerPrimitive после инициализации zone.js вы получите сообщение типа:

TypeError: "detachedCallBack" is read-only.  

Так вот почему его нужно определить раньше.Смотрите эту ссылку для более подробной информации.Эта проблема detachedCallBack является проблемой только при работе в угловом контексте.

Вот и все.После того, как вы это сделаете, вы должны увидеть любые объекты в виде рамки, определенные в ваших частичках, которые появляются на сцене.Использование parials становится очень важным, потому что кажется, что a-frame эффективно поддерживает только один <a-scene> на страницу (и, поскольку Angular - это SPA, в действительности это одна физическая страница с несколькими логическими страницами в качестве маршрутов).Смотрите здесь и здесь для более подробной информации.Таким образом, фактически вы должны вставить логические сцены в качестве вспомогательных сцен и использовать атрибуты видимости, чтобы определить, что на самом деле отображается.

Что-то вроде:

<a-scene>
  <a-entity laser-controls="hand: right"></a-entity>
  <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
  <a-circle
    position="0 3 -5"
    color="#a3ff6e"
    (click)="toggleSubScenes($event)"
  ></a-circle>
  <app-sub-scene-a></app-sub-scene-a>
  <app-sub-scene-b></app-sub-scene-b>
</a-scene>

Где toggleSubScene выглядит примерно так:

  toggleSubScenes() {
    console.log(`WrapperSceneComponent.toggleSubScenes: entered`);
    let ssa = document.querySelector('app-sub-scene-a');
    let ssb = document.querySelector('app-sub-scene-b');
    let ssaVisible : any= ssa.getAttribute('visible');
    let ssbVisible : any= ssb.getAttribute('visible');

    ssa.setAttribute('visible', ssaVisible ? 'false' : 'true');
    ssb.setAttribute('visible', ssbVisible ? 'false' : 'true');
  }

Эмпирически, я не могу поддерживать "vr-mode" через несколько "a-сцены », не требуя пользовательского жеста для повторного ввода VR.Таким образом, я должен создать одну глобальную «a-сцену» и определить мои предыдущие автономные сцены как частичные суб-сцены, внедренные в основную a-сцену.

0 голосов
/ 07 июня 2018

Итак, после большого сеанса отладки, я думаю, я выяснил, в чем проблема.Единственная проблема заключается в том, что «решение» на самом деле не лучше, чем обходной путь создания компонента напрямую.

Предостережение: конечно, само собой разумеется, что мой анализ является предположением с моей стороны (основанным на эмпирических наблюдениях), и я не претендую на то, чтобы быть авторитетом в самом исходном коде a-frame.

TLDR;
appendChild , по-видимому, является основным зацепом a-frame для вставки себя в DOM и сцену.Тем не менее, a-frame мудро ограничивает расширенную (или «толстую») версию appendChild тегами, распознаваемыми a-frame (например, «a-cube», «a-scene», «a-бла».. и т.д.).Он не использует эту «толстую» версию appendChild для элементов без рамки.Любой элемент, введенный с помощью angular (например, «app-my-angular-tag»), будет не распознаваться a-frame и, таким образом, будет просто использовать «тонкую» или стандартную версию appendChild , таким образом вставляя в только DOM .

Detail
Похоже, что A-рамка имеет расширенные версии createElement , appendChild и, вероятно, getAttribute и setAttribute . createElement вставляет object3D в элементы, и кажется, что angular использует createElement для создания своих элементов, что объясняет, почему я видел неинициализированные object3D в партиалахЭлементы a-frame (каждый элемент должен быть создан индивидуально, таким образом, даже если родительский элемент не распознается a-frame, дочерние элементы, которые являются a-frame, будут обрабатываться как таковые).

Примечание:родительский компонент обернут в тег a-scene и, таким образом, распознается с помощью вставки символа a-frame и fat.Это объясняет, почему родительский компонент правильно создан .Частное всегда заключено в некоторый родительский элемент, созданный angular.Если часть была обернута в какой-то тег a-frame, такой как a-sub-scene , то я ожидала бы, что он будет правильно введен.

Однако, поскольку дерево созданных элементов вставляется как один фрагмент, a-frame будет смотреть только на тип родительского элемента, который будет не-a-frame в случае введенного углатег.Таким образом, как объяснено в TLDR, полная версия appendChild не вызывается , и среда object3D не настроена, и элемент не внедряется в сцену в виде кадра.

Решение (я)
1) После загрузки a-сцены вы должны зациклить элементы a-frame введенного углового элемента и повторно применить appendChild, который теперь будет использоватьтолстую версию и правильно вставьте в сцену (полный пример здесь )

Вот некоторые доказательства концепции кода:

Родительский компонент:

 <body>
    <a-scene debug="true">
      <app-config-panel></app-config-panel> <-- ng inserts here
      <a-circle radius="0" id="config-hook-circle"></a-circle> <--Note: reinsertion point
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>

Шаблон внедренного дочернего компонента (config-panel.component.html):

<a-entity id="acp">
  <a-sphere color="yellow" radius=".2" position="0 0 -3.5"></a-sphere>
  <a-cylinder color="red" height=".5" radius=".3" position="-1 2 -3.5"></a-cylinder>
</a-entity>

Повторный ввод вызова после загрузки компонента (в родительском компоненте):

ngOnInit() {
document.querySelector('a-scene')
  .addEventListener('loaded', () => {
    this.reinjectConfigPanel();
  })
}

И коддля повторного введения (примечание: привязано к индивидуальной структуре шаблона, очень хрупкое и не общее решение):

  reinjectConfigPanel(){
    let acp=document.querySelector('app-config-panel'); 
    let acpClone  = (acp.cloneNode(true) as Element);
    let hook = document.querySelector('#config-hook-circle');

    let numChildren = acpClone.children[0].children.length;
    for (let i=0; i < numChildren; i++) {
      // Note: after you appendChild node 0, then the next node becomes 0, so we always
      // use child 0
      hook.appendChild(acpClone.children[0].children[0]) // uses fat version of appendChild
    }
  }

Как видите, к тому времени, когда все сделано, вы по существу повторно вводитевсе, так что вы могли бы просто ввести его, режв первую очередь.Тем не менее, этот работает , и я хотел показать, что это возможно (а также проверить мою теорию о толстом appendChild ).Там может быть много преимуществ, таких как привязка переменных, которые легче сделать в шаблоне, поэтому могут быть случаи, когда вам нужно сделать что-то вроде этого.

2) Получить A-Frame, чтобы распознать вашугловой тег как «официальный» тег a-frame.Я на самом деле не исследовал эту линию атаки, кроме как предположить, что вызов «registerPrimitive» может быть способом достижения этого:

AFRAME.registerPrimitive('app-config-panel', { defaultComponents: {}, mappings: {} })

Будем надеяться, что, делая это, вы обманываете a-frame, используя толстый appendChild, и, таким образом, добавляете свою часть к a-сцене.Однако это должно быть сделано до того, как a-frame создаст сцену, и единственный способ, которым я мог бы подумать, это изменить сам источник a-frame (?)

...