I am using Scrollmagic in an angular js app. My app has a sidebar on right with infinite scrolling. There are sections which stick for a particular amount of time then unstick like below
Section A comes into view and sticks
Section A remains sticky for few pixels and then unsticks
Section B appears and sticks after few pixels
Section B remains sticky for few pixels and then unsticks and so on..
So in effect i create multiple scenes and add it to the scrollMagicController. I am now suspecting memory leaks due to scrollmagic retaining DOM nodes and heap profiler of devtools proves this. I know that there is a destroy method for scene and controller which needs to be called to clear things up but i can't figure out how to destroy multiple nodes. Below is what i have tried
var scrollMagicCtrl = new ScrollMagic.Controller();
//create scene dynamically whenever stickyContainerInView event fires
scope.$on('stickyContainerInView', function(event, inViewElement) {
new ScrollMagic.Scene({
triggerElement: someElement, //Selector or DOM object that defines the start of the scene
triggerHook: 'onLeave', //sets the position of trigger hook w.r.t viewport
duration: someDuration, //The duration(in pixels) for which the element will remain sticky
offset: -60 //Offset Value for the Trigger hook position
})
.setPin(someContainer)
.addTo(scrollMagicCtrl);
});
//clean up things on angular's destroy method
$scope.$on("$destroy", function() {
scrollMagicCtrl.destroy();
scrollMagicCtrl = null;
});
I guess i just need to call the controller's destroy method and it should automatically destroy all the scenes added to it but unfortunately it doesn't work that way.
Screenshot of heap profiler detached DOM node tree
Any help on how should i clear up things ?
Related
I have a list of charts. I use Chart.js to create those charts. Since my list can have 1 to 100 or more entries initializing all charts at once would not be smart because that would make the ui freeze for a long time. So instead I thought it would be much better to only initialize those charts which are visible inside the view bounds of the browser so that for example only the first chart is getting initialized and when the user scrolls down and the second canvas becomes visible the second is getting initialized and so on.
I have everything setup but the only problem that I have right now is: how can I create an eventlistener or anything similiar which I can add to each canvas element that gets triggered when a canvas becomes visible inside the view bounds of the browser so that i can perform the chart initialization for that canvas?
I'm the author of OnScreen, a small library that can call a callback function when a HTMLElement enters the viewport or the boundaries of its container.
// Uses ES6 syntax
import OnScreen from 'onscreen';
const os = new OnScreen();
os.on('enter', 'canvas', (element) => {
if (!element.chartInitialized) {
// Initialize the chart
// Update the `chartInitialized` property
// to avoid initializing it over and over
element.chartInitialized = true;
}
});
For more information, take a look at the documentation. Don't forget to check the demos repo for a couple simple examples.
I have used the onScreen jQuery plugin.
It is very easy. You just have to call for each canvas this:
$('elements').onScreen({
container: window,
direction: 'vertical',
doIn: function() {
// initialize canvas
},
doOut: function() {
// Do something to the matched elements as they get off scren
},
tolerance: 0,
throttle: 50,
toggleClass: 'onScreen',
lazyAttr: null,
lazyPlaceholder: 'someImage.jpg',
debug: false
});
I use moveMouseTo but it doesn't seem to work. This is my code. Does anyone able to see what is wrong with it? The assert should work and not return error, because if you try to scroll down the page at www.keylocation.sg, it will shows a navigation bar.
Thanks before.
define([
'intern!object',
'intern/chai!assert',
'./util',
'intern/dojo/node!fs'
], function(registerSuite, assert, util, fs) {
var suite = {
name: 'home-navbar',
afterEach: util.checkJSErrors,
// testing the visibility of navigation bar in the home page
'Home page navigation bar: navigation bar visibility': function() {
var remote = this.remote
.setWindowSize(1024, 768)
.get('about:blank')
.get('https://www.keylocation.sg');
this.timeout = 300000;
return remote
// check: Home page loads, navbar is not visible
.findById('header-menu').isDisplayed().then(assert.isFalse).end()
// check: Scroll down to next page, navbar becomes visible
.moveMouseTo(0,1000).end()
.findById('header-menu').isDisplayed().then(assert.isTrue).end();
}
};
registerSuite(suite);
});
Your test is probably failing because of how the WebDriver server you're using determines element visibility. The header menu on that page is initially not visible because it has a negative top margin, which moves it outside of the viewport. However, according to the WebDriver spec, an element that's outside the viewport because of a negative margin isn't necessarily considered invisible. Chrome's and Firefox's WebDriver servers, at least, say it's visible. This is a WebDriver issue rather than an Intern issue; Intern is basically just asking the WebDriver server "is this element visible" and telling you the answer.
Since isDisplayed doesn't seem like it will work in this case, you could instead check whether the element has the disabled class, which is what causes it to have the negative margin.
Unfortunately, trying to simply move the mouse 1000 pixels outside of an element context doesn't scroll the page. When you don't give moveMouseTo an element, it moves within the current context element (the last thing that was found). When there's no context, it's moving within the outermost element, which in this case is only 632px high. You'll need to set the context to an element that's tall enough to contain your movement offset, or you can find an element at the bottom of the page, like the footer, and move the mouse to that:
.findByCssSelector('.wrapper')
.moveMouseTo(0, 1500) // 1000 pixels is too small to show the scrollbar
.end()
or
.findByTagName('footer')
.then(function (footer) {
return this.parent.moveMouseTo(footer);
})
.end()
Your test has a few other issues you may want to correct as well. The initial get('about:blank') isn't necessary. There's no reason to split the two halves of your command chain; the timeout applies to the whole chain whether it's split or whole. You don't need an end after the moveMouseTo; end is for popping elements off the command chain's context, and moveMouseTo doesn't add anything to the context.
I have a panel with 3 items in it. A panel then a horizontal splitter then a panel. This layout works fine and the user can easily drag the splitter up and down to resize the adjoining panels.
But how do I set the position of the splitter programmatically? Ideally I'd like to animate the splitter to its new position.
The splitter has a method setPosition, which also takes an animate argument. But this method simply moves the splitter without changing the heights of either panel. So the splitter is now floating over the top of one of the panels. In the afteranimate listener I've tried all manner of doLayout, updateLayout, panel.setHeight, etc... but nothing seems to affect the panel sizes. According to the console the code is run, it just doesn't appear to do anything.
So what is the method for changing the position of a horizontal position between two panels?
This is using Ext 4.1.1a.
splitter.setPosition(0,30,{
listeners:{
beforeanimate: function(){
console.log('animate');
},
afteranimate: function(){
console.log('finished');
bottomPanel.setHeight(500); //Does nothing
splitter.updateLayout(); //Does nothing
rightColumn.doLayout(); // Puts splitter bar back to the original position
}
}
});
I have solved a very similar problem with ExtJS 4.1.3. I have two panels separated by a vertical splitter and I wanted the splitter position to persist across page refreshes.
After trying for some time trying to hook into the splitter itself, I abandoned the idea. The way I have been able to set the splitter position programmatically starts by listening to the resize event of one of the panels.
I store the width of the sidebar with the ExtJS state manager. If the new width parameter is undefined, then this is a page reload situation and I grab the last saved sidebar panel width from the state manager. I set the width of the sidebar to the saved value, then set the splitter position to 20 pixels more than the width (to account I assume for the scrollbar.)
Inside the sidebar panel:
resize: function (sidebar, newWidth, newHeight, oldWidth, oldHeight) {
var sidebarWidth = newWidth;
if (oldWidth === undefined) {
// page reload. restore width from state manager
savedWidth = Ext.state.Manager.get('sidebar_width');
sidebarWidth = savedWidth === undefined ? sidebarWidth : savedWidth;
}
sidebar.splitter.setPosition(sidebarWidth + 20);
sidebar.setWidth(sidebarWidth);
Ext.state.Manager.set('sidebar_width', sidebarWidth);
}
I had avoided setting a listener inside a panel because I didn't know how to get a handle to the splitter object. The breakthrough came when I discovered that the context parameter sent into the resize event (named sidebar in the example code provided) holds a reference to the splitter object.
This solution worked for me and hopefully it will be helpful to others with this question.
I have a canvas created with KineticJs Stage. on this stage I have three layers. One is the background and is always on. The other two are overlays and the visibility is toggled by a checkbox. Any time the parent div of this stage resizes I redraw the entire stage to keep my layout correct. Here are the two situations where my toggling works:
1. before resize and redraw.
2. If I don't toggle it at all before the redraw.
Here is where it does not work:
1. Toggle layer on then off. Resize canvas to trigger redraw. Then try and toggle back on. In this case the visible attribute gets set to true when I call show() but the layer does not actually show up.
Stepping through the code I can not find any difference in the layer during any of the above scenarios. I did however notice that the index of each layer gets incremented each time it is rebuilt even though I have instantiated new instances of the stage and every layer each time I rebuild.
Can anybody tell me why the index increments even though everything has been destroyed and why the layer is not showing up? I was thinking zindex but this never seems to change and should be showing up.
here is what I do before every rebuild:
stage = new $window.Kinetic.Stage({
container: 'canvas',
x: 0,
y: 0,
width: parent.clientWidth,
height: parent.clientHeight
});
var layer = new $window.Kinetic.Layer();
erosionLayer = new $window.Kinetic.Layer({
visible: scope.erosionVisible
});
Here is where I toggle it:
scope.$watch('erosionVisible', function(val) {
if (!erosionLayer) return;
var showErosion = false;
if (!scope.erosionVisible) {
showErosion = false;
} else if (scope.erosionVisible) {
showErosion = true;
}
if (showErosion) {
erosionLayer.show();
} else {
erosionLayer.hide();
}
});
FYI, This is in an angularJs directive.
fixed the issue. for some reason I need to call draw on the layer when toggling if the stage has been rebuilt.
I want to provide the user with the experience of scrolling through content, but I would like to load the content dynamically so the content in their viewing area is what they would expect, but there is no data above or below what they are looking at. For performance reasons, I don't want that data loaded. So when they scroll down new data gets loaded into their view, and data previously in their view is discarded. Likewise when scrolling up. The scroll bar should represent their location within the entire content though, so using "infinite scrolling" or "lazy loading" does not look like what I need.
My solution may be that I need to re-architect things. As of now, my project is a hex-viewer that allows you to drop a binary file onto it. I create html elements for every byte. This causes performance issues when you end up with a 1MB file (1,000,000+ DOM elements). One solution would be to not use DOM elements/byte but I think this will make other features harder, so I'd like to just not display as many DOM elements at once.
Make a div, set overflow to scroll or auto. As user scrolls you can change the content of the div.
You could look at yahoo mail (the JavaScript based one) to see how they do it (they add rows with email as you scroll).
You don't necessarily need custom scroll bars.
You could look for some code here for custom scroll bars:
http://www.java2s.com/Code/JavaScript/GUI-Components/Scrolltextwithcustomscollbar.htm
or here:
http://www.dyn-web.com/code/scroll/
I'm looking for an answer to this question as well so I'll share where I'm at with it.
I have a large amount of content I want to display vertically and have the user scroll through it. I can load it all into the DOM and scroll normally but that initial creation phase is horribly slow and scrolling can awfully slow also. Also, I will dynamically add to it as I stream more data in.
So I want the same thing which is to be able to dynamically populate and update a non-scrolling area with content. I want to make it seem as if the user is scrolling through that content and have a model (which has lots of data) that is kept off the DOM until it would be seen.
I figure I'll use a queue concept for managing the visible DOM elements. I'd store queueHeadIndex and queueTailIndex to remember what off-DOM elements are shown in the DOM. When the user scrolls down, I'd work out what whether the head of queue falls off the screen and if it does update queueHeadIndex and remove it's DOM element. Secondly I'd then work out whether I need to update queueTailIndex and add a new element to the DOM. For the elements currently in the DOM I'd need to move them (not sure if they need animation here or not yet).
UPDATE:
I've found this which seems to have some promise http://jsfiddle.net/GdsEa/
My current thinking is that there are two parts to the problem.
Firstly, I think I want to disable scrolling and have some sort of virtual scrolling. I've just started looking at http://www.everyday3d.com/blog/index.php/2014/08/18/smooth-scrolling-with-virtualscroll/ for this. This would capture all the events and enable me to programmatically adjust what's currently visible etc. but the browser wouldn't actually be scrolling anything. This seems to provide mouse wheel driven scrolling.
Secondly, I think I need to display a scroll bar. I've had a look at http://codepen.io/chriscoyier/pen/gzBsA and I'm searching around more for something that looks more native. I just want it to visually display where the scroll is and allow the user to adjust the scroll position by dragging the scroller.
Stackoverflow is insisting I paste code so here is some code from that codepen link above
var elem = document.getElementById('scroll-area'),
track = elem.children[1],
thumb = track.children[0],
height = parseInt(elem.offsetHeight, 10),
cntHeight = parseInt(elem.children[0].offsetHeight, 10),
trcHeight = parseInt(track.offsetHeight, 10),
distance = cntHeight - height,
mean = 50, // For multiplier (go faster or slower)
current = 0;
elem.children[0].style.top = current + "px";
thumb.style.height = Math.round(trcHeight * height / cntHeight) + 'px';
var doScroll = function (e) {
// cross-browser wheel delta
e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
// (1 = scroll-up, -1 = scroll-down)
if ((delta == -1 && current * mean >= -distance) || (delta == 1 && current * mean < 0)) {
current = current + delta;
}
// Move element up or down by updating the `top` value
elem.children[0].style.top = (current * mean) + 'px';
thumb.style.top = 0 - Math.round(trcHeight * (current * mean) / cntHeight) + 'px';
e.preventDefault();
};
if (elem.addEventListener) {
elem.addEventListener("mousewheel", doScroll, false);
elem.addEventListener("DOMMouseScroll", doScroll, false);
} else {
elem.attachEvent("onmousewheel", doScroll);
}
I imagine I'll have one class that listens to scroll events by either the virtual scroll method or the ui and then updates the ui scroller and the ui of the content I'm managing.
Anyway, I'll update this if I find anything more useful.
I think avoiding using DOM elements/byte is going to be the easier solution for me than creating a fake scrolling experience.
UPDATE: I ultimately solved this as explained here: Javascript "infinite" scrolling for finite content?
You're taking about using some serious javascript, specifically AJAX and JSON type elements. There is no easy answer to your questions. You'd need to do a lot of R&D on the subject.