AngularJS virtual infinite scrolling with dynamic row height - javascript

I want to implement infinite scroll using virtual ng-repeat.
But there is one caveat - I have dynamic row height.
Imagine simple chat. Length (content / height) of messages may vary.
I wonder how it's implemented in most messengers and G+, for example. They all had the same issue.
In G+ the feed has blocks with different height. Do they use Virtual Repeat?
md-virtual-repeat and collection-repeat don't work for me because they want constant height for all rows (messages)
Even if I use them, there some scroll positioning issues.
They take dimensions from first element if none is specified explicitly.
ngInfiniteScroll has nothing common with word virtual. It spawns enormous amount of watchers.
Any ideas?
Thanks!

Related

infinite vertical scroll on a non-image div

I'm using a library called ParticlesJS for part of the background of my website - this library dynamically generates a canvas element sized according to its parent, and fills it with animated particle effects, creating a neat effect. With that said, I have run into some practical issues when trying to use it as the background:
If the canvas element is the same size as the content, the visuals become pixelated and distorted if the height changes, such as with the addition of new content. Reloading the library is not a solution to this as it creates a visually distracting effect.
If the canvas element is an arbitrary extreme height and not sized according to the content (with the overflow simply hidden), the performance of the website suffers, as the library consumes excessive CPU power.
If the canvas element is simply given a fixed position in CSS, performance is good and it sticks, but it looks out of place as everything behind it moves during scrolling.
After some consideration, it seems like the best way to make it work is to give it a modest size (like 200% page height), and then make it repeat infinitely during scrolling - performance would be acceptable, and there wouldn't be any distortion. However, I can't find any way to do this - I'm aware that there's a background-repeat property in CSS, but that seems to only work for images.
Is there any way to do what I'm trying to accomplish? Both CSS and JS based answers are welcome.
After some trial and error, it looks like the only means of accomplishing what I'm trying to do is as follows:
Create 3 or so background divs, each the size of the view port, and stack them vertically
Record user scrolling activity, and set a trigger for when a user has scrolled a height equal to the height of the view port
when the trigger is hit, place the div that just left the view port at the end of the list, and insert an empty spacer div where it used to be
If done correctly, this creates an effect where the user is apparently scrolling through an infinite background, when it's really just the same 3 or so divs being shuffled over and over. Going in reverse is the same principle.
Not sure how to make this work with in a system that also has scroll position restoration, but it could probably be done by waiting for page loads and then dynamically inserting enough spacers to move the background divs to the appropriate position in the view port.
The downside to using animated effects that rely on viewport dimensions is that the user may resize the browser and wreck your animation so you have no choice but to catch any viewport resizing in which case you may have to reload everything or recalculate!
You can't have the cake and the cherry on top unfortunately, so you'll either have to abandon the idea of "impressive effects" because they are impractical or take action...
document.body.onresize=function(){Adjustments();};
function Adjustments(){
var W=Container.offsetWidth, H=Container.offsetHeight;
// You've now got the new resolution so go for your life!
}

AngularJS: virtual repeat with row with different heights

I'm building an hybrid app using ionic and AngularJS (AngularJS-material). This app also has an integrated chat built with Node.js and socket.io.
I have the problem now that with only 200 messages the app gets very slow to load all the messages (200ms in Browser -> 4sec in app, even with CrossWalk, and with track by message.id) and also typing in the the textarea to insert the message is slowed down.
I have two solutions to resolve this:
Virtual Repeat (md-virtual-repeat)
Infinite Scroll (ion-infinite-scroll)
1) I think that virtual repeat would be the best solution (I have already implemented it on another page and it scrolls 1500 items like a charm) but the problem is that the messages can have different heights based on their lenghts and md-virtual-repeat requirements are that all the elements must have the same height to work.
2) So maybe we can pivot to the Infinite Scroll method but the problem now is that doing it with the ion-infinite-scroll directive gets a bit tricky since a chat needs to trigger the loadMore() when reaching the top and not the bottom.
So my question is: Does anybody have a workaround to have a smooth/fast ng-repeat inside a chat using or a virtual-repeat directive that can handle different heights or an infinite scroll that works at the top ?
Efficient scroll lists like md-virtual-repeat or collection-repeat need to know the height of each item in order to work. That’s because they need to know the scroll position, e.g. to show a scrollbar or to be able to skip frames for quick swipe-down motions. The scroll position itself can only be found if you know both how much has been scrolled (we need the height of elements above) and how much there is left to scroll (we need the height of elements below).
What you can do is use a factory to compute the height of each element before injecting them into the loop. This can be done by creating a container with the same properties as the target container (e.g. CSS classes), appending the newly-loaded elements, compute their height (using element.offsetHeight), and removing the container after.
Be aware that this is quite heavy and will likely cause a small lag spike.
Couple of things you could try to speed it up.
One would be to use something like quick-ng-repeat: https://github.com/allaud/quick-ng-repeat instead of built in angular js ng-repeat
Another would be to use one time binding where ever possible to prevent angular from constantly looking for changes during every digest cycle: https://docs.angularjs.org/guide/expression#one-time-binding
And of course, if it's possible, try using chrome's developer tool profile option to find out which of the functions are slowing the application down ; )
PS: Might be worth checking out this thread to see how reverse infinite scrolling could be implemented: Implementing a reverse infinite scroll using ngInfiniteScroll directive in AngularJS
Have you taken a look at React.js as a solution? It uses a virtual DOM which makes updating long lists more efficient.
There is an open-source repo on GitHub that mixes Angular and React, called ngReact.
overview:
http://ngreact.github.io/ngReact/
docs:
http://ngreact.github.io/ngReact/docs/ngReact.html
repo:
https://github.com/ngReact/ngReact
Hope this helps.
Using separate binders like rivets could be a good solution,its easy to integrate and its having rv-each to loop
I think the ionic directive collection-repeat might be what you're looking for.
collection-repeat allows an app to show huge lists of items much more
performantly than ng-repeat. It renders into the DOM only as many
items as are currently visible. This means that on a phone screen that
can fit eight items, only the eight items matching the current scroll
position will be rendered.
Look at angular-vs-repeat angular-vs-repeat
Demo: demo
I use line length for calculation height of items.
It's very approximate method. In our app we know, that one English character with font-size 15px will have width about 6.7px (that's fine, if you use monospace font, but it's not our case). Also we always know width of each segments and height of one text line.
itemHeight = commulativeTextLenght / lineWidth + paddingsOfItem;
Also line break stile can affect your calculation. In general we had a not bad method to calculate heights of about 8000 text paragraphs.

How can I split HTML content into pages that fit the screen?

I'm making a book reader web application. From the server I get a long string with all of the book text in HTML format. It looks something like this:
<div><p>Alice was beginning....</p></div></div>to get very ... </div>
What I want is to split this text into pages. I want to get something like this:
<li id='page1'>... text ...</li>
<li id='page2'>... text ...</li>
...
A single page should fill the viewport. Users shouldn't be able to scroll the book text. Instead, they should be able to move between the pages using buttons.
I need some function to measure all content, split it into pieces of same maximum height, and tag the pieces with page numbers. It must also take into account pictures and long paragraphs (which may not fit on a single page).
How can I accomplish all of this?
To find out the dimensions of each bit of content, you'd have to let the browser actually render it and then measure it. Additionally, if you want to break in the middle of flow (e.g. in the middle of a paragraph), things get pretty difficult. (You cannot measure individual characters/words without wrapping them in elements.) You'd also have to disable zoom, etc., so that your measurements have some lasting meaning. All in all, HTML rendering engines are specifically tailored to render flow content, which is pretty much the opposite to paged content.
However, there are some approaches to paging, you could take, neither of which actually deals with rendering the whole text and taking measurements.
Scroll Override
One approach I'd try would be to simply
render all text into a container,
style the container in such a way that the scrollbar is not visible (e.g. by pushing it off the screen),
disable manual scrolling, and
provide "paging" buttons that programatically scroll by exactly the height of your "page".
This solution has the advantage of being simple to implement, but it's not perfect. Images can be rendered across the break and the user wouldn't be able to see them whole.
Columns
The only other approach I could think of at short notice is using columns. You'd have to
render all text into a container,
style the container to be "infinitely" wide and have "infinite" number of columns, each the width of the page,
absolutely position the container in its parent,
make your "paging" buttons move the container horizontally by the width of the page.
This solution needs modern browsers with support for columns, but using columns solves the problem of properly breaking flow content into pages. I'd recommend trying this first, if at all possible.

Auto height virtual list for Mobiles to display large list

I have a requirement where I have to display large list of items that may have different heights and also items will be adding to end of list dynamically. Previously I have written my own virtual list and that performed very well in Desktop browsers, but I observed flickering in with that list in Safari browser as I was doing a lot many things on scroll. As safari adds effects while scrolling it was conflicting with native scroll effects. I have found an interesting way of inserting and removing nodes while scrolling here. But this is designed for lists that has fixed height elements, not for auto height elements(Even solution I have implemented) because at the start we calculate the total height like below
this.totalRows = config.totalRows || (config.items && config.items.length);
var scroller = VirtualList.createScroller(itemHeight * this.totalRows);
this.container = VirtualList.createContainer(width, height);
this.container.appendChild(scroller);
And while scrolling we find scroll item as below
var first = parseInt(scrollTop / itemHeight);
But, this is what scaring me because for auto height elements and I completely clueless on how can I calculate first item, and how to find total height of dummy scroller.
Anybody has implemented this kind of solution for mobiles before, and are there any alternatives to this approach or any plugins available?
Sounds like you should be able to do correct calculating by always run a loop through each elemnt to get every elements actual height. Auto height elements still has a height. Make sure to do some real testing and there are different ways to calculate height. I do it in jquery using outerHeight to make sure it calcualtes with padding. It would be easier if you shared some hardcoded html/js and point on a specific problem with the height.

Looking for ideas to quickly flow content

I'm writing some code that wraps various content into columns of text (and images, videos, etc). The code works fine, but due to the algorithm I'm using it's rather slow, specifically this general logic:
add something (text for this example) to a column
check to see if column.scrollHeight > column.offsetHeight (this requires a DOM reflow)
if yes, start to binary split the text until it's shorter
Basically my issue is that I'm adding an unknown amount of text to a column, so after each chunk of text I check the column's scroll height which requires the browser to actively reflow the DOM in order to give me the correct scrollHeight. So I have 50-100 or more reflows in order to properly lay everything out.
Any general ideas on how to avoid most of these?
You could render the content multiple times. Since the first time would cache it, this should be fairly fast. The reason for the multiple rendering would be as follows.
Render the original content in a hidden area
Check to see what the column width is compared to content
Overlay the content over the column, but beneath the page. This
will cut off part of the content that is overflowing. You can accomplish with
z-indexing or with overflow: hidden;
Based on what the check from step 2 was, overlay a copy of the content with
the calculated offset in the next column in the same fashion, hiding the
extra content.
Keep track of the rendered content versus total content so you can tell how many
columns you need to do this to if there are multiple columns.
Maybe this is the same thing Travis J is suggesting, but I'm not sure, I don't quite understand his solution.
You could render everything first, on a single column, then loop through the elements top-down to know when to split, based on your desired column height versus each element's offsetTop plus height. When you find an element to break at, cache its position and go on. At the end you should have an array with the list of elements to break at, so you can actually split the content in columns.
Does this make any sense to you?

Categories