0

I'm building a series of web apps inside an Angular SPA. Each of these applications sit inside an Angular template, and navigation between them strips and replaces appropriate DOM elements and run scripts from controllers.

My question here is more of a general one: upon navigating away from a Three.js scene and navigating back, obviously the DOM elements and scripts themselves are re-run, resulting in the scene starting from "scratch". Is there a way to keep the scene running in the background, so that the user can navigate back to an app and have it be just where they left off?

Is this even advisable? I'm aware that the requestAnimationFrame method stops animating when a user navigates to another tab, but how does this work in an Angular context?

As an example, here's the WIP web page. Try navigating away from the StarFinder tab and then back, and you'll see what I mean.

EDIT: Here's a JSFiddle to demonstrate what I mean (in this case it modifies the DOM without Angular).

Really, the bit in question is the renderer, which gets re-initiated upon navigating back:

function render() {
        requestAnimationFrame(render);
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        renderer.render(scene, camera);
    }
render();
j_d
  • 2,818
  • 9
  • 50
  • 91
  • Please add meaningful code and a problem description here. Don't just link to the site that needs fixing - otherwise, this question will lose any value to future visitors once the problem is solved. Posting a [Short, Self Contained, Correct Example (SSCCE)](http://www.sscce.org/) that demonstrates your problem would help you get better answers. For more info, see [Something on my web site doesn't work. Can I just paste a link to it?](http://meta.stackexchange.com/questions/125997/) Thanks! – Cerbrus Jun 25 '15 at 14:21
  • @Cerbrus The linked site isn't one that "needs fixing", and this question is more general/theoretical than based on specific code. How would you like me to proceed? – j_d Jun 25 '15 at 14:22
  • The link in your question will lose it's value when you fix the problem, so it doesn't help the question for future visitors. Can you post a SSCCE on this question? – Cerbrus Jun 25 '15 at 14:24
  • @Cerbrus Point taken, but due to the nature of the question any example won't be very simple. I'll put one together in JSFiddle. – j_d Jun 25 '15 at 14:27
  • @Cerbrus I've updated the original question with a JSFiddle. – j_d Jun 25 '15 at 15:03

2 Answers2

0

If you're using ui-router, you can load the rest of your site in a nested view and then just hide the div in the parent view that has your animation without actually unloading it. Put the requestAnimationFrame(render); in an if statement that checks a boolean flag. Then update that flag when your route changes. See my answer here to see how to detect route changes. If you are navigating to your scene, then also call render() to start up the animation again.

Another option may be to allow it to unload, but save any large assets and/or settings for your animation in localStorage so your startup time is less.

Community
  • 1
  • 1
adam0101
  • 29,096
  • 21
  • 96
  • 174
  • I'd like a seamless re-entry to animation, so I'll check out your first suggestion. Cheers. – j_d Jun 25 '15 at 15:15
0

angular destroy the DOM when recompiling a template and THREE.js use webGL renderer which is binded to the canvas.

If you have only one canvas rendering at a time for THREE.js, I would recommend to use an external canvas and move it in a directive upon creation and moved back to a static container upon removal (with $destroy)

try this :

create a directive that listen to emits for show/hide events and create a functional 3d canvas

then place that directive selector in the DOM outside of the changing template

like this

app.directive('threejsCanvas', function directive() {
  return {
    link: function(scope){
      var scene = new THREE.Scene();
      
      // create a temp holder to save the canvas outside of canvas
      var tempHolder = document.createElement('div');
      tempHolder.style.display = 'none';
      window.document.body.appendChild(tempHolder);
      
      var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
      var renderer = new THREE.WebGLRenderer();
      document.body.appendChild( renderer.domElement );
      
      scope.$on('start:webgl', function(view, callback) {
        view.appendChild(renderer.domElement);
        
        // you may do somthing like ...
        // renderer.setSize( view.innerWidth, view.innerHeight );
        // and change camera ratio if it have to change
        
        // pass the arguments to the caller's callback so the script can interact with the canvas singleton
        callback({
          scene: scene,
          camera: camera,
          renderer: renderer
        });
      });  
      
      scope.$on('$destroy', function() {
        // move canvas away from template so it's not destroyed
        tempHolder.appendChild(renderer.domElement);
      });
    }
  };
});
<div class="container">
  <threejs-canvas></threejs-canvas>
  <div ng-view></div>
</div>

so you just have to emit from your controller the element in which you want the canvas to render and pass a callback to get the THREE.js objects

zeachco
  • 754
  • 4
  • 16
  • I like this approach too. Maybe you could combine it with my solution and pause the rendering while it's in `tempHolder` to keep performance on the website snappy. – adam0101 Jun 25 '15 at 15:23