I am working on web application which contains different number of nested frames and iframes.
Currently when web page renders browser renders scroll bars against them. Sometimes count is 4-5 scroll bar in a page which looks very ugly. So I want to remove these scroll-bars through Javascript.
To understand the problem, Please see below example of web page with frame/iframe hierarchy.
Frame1
--Frame2
--Frame3
--Frame4
--Frame5
My approach :
Start from the root document and search all frames/iframes from the page and put these frame node in Array with additional variable called level.
After completion of this I am sorting the array on level. So after this activity array will look like.Below
0> Frame5 node, level=3
1> Frame4 node, level=3
2> Frame3 node, level=2
3> Frame2 node, level=2
4> Frame1 node, level=1
After this I am considering two option to remove scroll-bar
Option #1 :
start from 0th element from array and get scrollheight of frame and set it to height property of Frame.
var frameScrollHeight=Frame5.contentWindow.document.body.scrollHeight;
Frame5.style.height =frameScrollHeight;
Do same procedure for every frame in array.
My assumption here is that If innermost frames height changes, Its parent nodes scroll Height must changes to true scrollheight.
e.g After setting the Frame5 and Frame4 height. when we fetch scrollheight of Frame3 it must be Frmae5 height +Frame4 height. But now its not reflecting the true scrollheight.
I am not sure what is wrong here. When we set Frame5 and Frame 4 height will Dom refreshes immediately? .
Am i using wrong property? because I verified all other proprieties also using script debugger like clientHeight,offsetHeight but no one equivalent to Frame5 height+ Frame4 height.
Option #2
As above approach is not working correctly I took another approach of adding previous level frames height manually and set it to parent node in next level.
e.g Get scrollheight of Frame5 and Frame4 and set it to Frame3 Also add height of Frame3 and Frame2 and set it to Frame1.
Option two is working now but still it has some drawback like all frames must be in hierarchical order and must not be in parallel to each other.
Also when I am setting scrollheight as height to Frame scroll-bar still appears as it is and I have to add some constant like 15 to scrollheight. So actual code is like
var frameScrollHeight=Frame5.contentWindow.document.body.scrollHeight+15;
Frame5.style.height =frameScrollHeight;
I don’t know what this 15 is. Is it a frameborder? Can I get this constant using any property of frame?
I will prefer option 1 but don’t know why this is not working.
If there is any other way to solve this problem please suggest.
Why not just use
html,body{overflow:hidden}
in the CSS for the iframes?
I had the same problem and the following function (using jquery) worked for me after I converted all frames to iframes. This function will resize every frame so that there is no need for scrollbars:
var resizeFramesSoAllContentVisible = function (win) {
for (var i = 0; i < win.frames.length; i++) {
var frame = win.frames[i];
var contentWindow = frame.contentWindow ? frame.contentWindow : frame.window;
resizeFramesSoAllContentVisible(contentWindow);
}
if (win.frameElement) {
var myFrameOuterHeight = $(win.frameElement).height();
var myFrameInnerHeight = $(win.document).height();
var myFrameOuterWidth = $(win.frameElement).width();
var myFrameInnerWidth = $(win.document).width();
if (myFrameOuterHeight != myFrameInnerHeight) {
$(win.frameElement).height(myFrameInnerHeight);
}
if (myFrameOuterWidth != myFrameInnerWidth) {
$(win.frameElement).width(myFrameInnerWidth);
}
}
}
Related
I'm updating a table header and its first column positions programatically based on how the user scrolls around to keep them aligned.
The issue I'm experiencing is that as soon as my data sets gets big enough, the scrolling gets more and more choppy/less smooth.
The relevant code is at the very bottom of the fiddle:
iScroll.on('scroll', function(){
var pos = $('#scroller').position();
$('#pos').text('pos.left=' + pos.left + ' pos.top=' + pos.top);
// code to hold first row and first column
$('#scroller th:nth-child(1)').css({top: (-pos.top), left: (-pos.left), position:'relative'});
$('#scroller th:nth-child(n+1)').css({top: (-pos.top), position:'relative'});
// this seems to be the most expensive operation:
$('#scroller td:nth-child(1)').css({left: (-pos.left), position:'relative'});
});
I know that this can be written a lot more efficent by caching the elements and so on. For example, I have tried saving the elements in to an array and updating their position in a more "vanilla" fashion:
headerElements[i].style.left = left + 'px'; // etc...
No matter how fast I make the callback, I'm still not happy about the result. Do you have any suggestions?
https://jsfiddle.net/0qv1kjac/16/
Just use ClusterizeJS! It can handle hundreds of thousands of rows and was built exactly for this purpose.
How does it work, you ask?
The main idea is not to pollute DOM with all used tags. Instead of that - it splits the list to clusters, then shows elements for current scroll position and adds extra rows to top and bottom of the list to emulate full height of table so that browser shows scrollbar as for full list
To be able to handle big amounts of data you need data virtualization. It has some restrictions, though.
First you need to decide the size of a view port. Let's say you want to render 10 items in a row and 20 items in column. It would be 10x20 items then. In you fiddle it's div with id wrapper.
Then you need to know total amount of data you have. From your fiddle it would be 100x100 items. And, also you need to know height and width of a item (cell). Let's take 40x120 (in px).
So div#wrapper is a view port, it should have fixed sized like 10x20 items. Then you need to set up correct width and height for table. The height of table would be equal to total amount of data in column including head by item height. Width for table would be total amount of items in single row by item width.
Once you set up these, div#wrapper will receive horizontal and vertical scrolls. Now you able to scroll left and bottom, but it will be just empty space. However this empty space is able to hold exact amount of data you have.
Then you need to take scroll data left and top (position), which comes in pixels and normalize it to amount of items, so you could know not how many pixels you've scrolled, but how many items you've scrolled(or rows if we scroll from top to bottom).
It could be done by division of pixels scrolled on item height. For example, you scrolled to left by 80px, that's 2 items. It means these items should be invisible because you've scrolled past them. So you know that you scrolled past 2 items, and you know that you should see 10 items in a row. That means you take your data array which has data for row with 100 items, and slice it like this:
var visibleItems = rowData.slice(itemsScrolled, itemsScrolled + 10);
It will give you items which should be visible in viewport at current scroll position. Once you have these items you need to construct html and append it to table.
Also on each scroll event you need to set top and left position for tbody and thead so they would move with scroll, otherwise you will have your data, but it will be at (0; 0) inside a viewport.
Anyway, code speaks thousand of words, so here's the fiddle: https://jsfiddle.net/Ldfjrg81/9/
Note, that this approach requires heights and widths to be precise, otherwise it will work incorrectly. Also if you have items of different sizes, this also should be taken into consideration, so better if you have fixed and equal sizes of items. In jsfiddle, I commented out the code which forces first column to stay in place, but you can render it separately.
It's a good solution to stick to some library as suggested in comments, since it handles a lot of cases for you.
You can make rendering even faster if use react.js or vue.js
This won't be the answer your are looking for but here's my 2 cents anyway.
Javascript animation (especially given the amount that the DOM has to render) will never be as smooth as you want it. Even if you could get it smooth on your machine, chances are that it will vary drastically on other peoples (Older PC's, Browsers etc).
I would see 2 options if I were to tackle this myself.
Go old school and add a horizontal and vertical scrollbar. I know it's not a pretty solution but it would work well.
Only render a certain amount of rows and discard those off screen. This could be a bit complicated but in essence you would render say 10 rows. Once the user scrolls to a point where the 11th should be there, render that one and remove the 1st. You would pop them in and out as needed.
In terms of the actual JS (you mentioned putting elements in to an array), that isn't going to help. The actual choppyness is due to the browser needing to render that many elements in the first place.
You're experiencing choppy / non-smooth scrolling because the scroll event fires at a very high pace.
And every time it fires you're adjusting the position of many elements: this is expensive and furthermore until the browser has completed the repaint it's unresponsive (here the choppy scrolling).
I see two options:
Option number one: display only the visible subset of the whole data set (this has been already suggested in another answer so I won't go futher)
Option number two (easier)
First, let animations on left and top css changes occurr via transitions. This is more efficient, is non-blocking and often let the browser take advantage of the gpu
Then instead of repeteadly adjust left and top, do it once a while; for example 0.5 seconds. This is done by the function ScrollWorker() (see code below) that recalls itself via a setTimeout().
Finally use the callback invoked by the scroll event to keep the #scroller position (stored in a variable) updated.
// Position of the `#scroller` element
// (I used two globals that may pollute the global namespace
// that piece of code is just for explanation purpose)
var oldPosition,
newPosition;
// Use transition to perform animations
// You may set this in the stylesheet
$('th').css( { 'transition': 'left 0.5s, top 0.5s' } );
$('td').css( { 'transition': 'left 0.5s, top 0.5s' } );
// Save the initial position
newPosition = $('#scroller').position();
oldPosition = $('#scroller').position();
// Upon scroll just set the position value
iScroll.on('scroll', function() {
newPosition = $('#scroller').position();
} );
// Start the scroll worker
ScrollWorker();
function ScrollWorker() {
// Adjust the layout if position changed (your original code)
if( newPosition.left != oldPosition.left || newPosition.top != oldPosition.top ) {
$('#scroller th:nth-child(1)').css({top: (-newPosition.top), left: (-newPosition.left), position:'relative'});
$('#scroller th:nth-child(n+1)').css({top: (-newPosition.top), position:'relative'});
$('#scroller td:nth-child(1)').css({left: (-newPosition.left), position:'relative'});
// Update the stored position
oldPosition.left = newPosition.left;
oldPosition.top = newPosition.top;
// Let animation complete then check again
// You may adjust the timer value
// The timer value must be higher or equal the transition time
setTimeout( ScrollWorker, 500 );
} else {
// No changes
// Check again after just 0.1secs
setTimeout( ScrollWorker, 100 );
}
}
Here is the Fiddle
I set the Worker pace and the transition time to 0.5 secs. You may adjust the value with higher or lower timing, eventually in a dinamic way based on the number of elements in the table.
Yes! Here are some improvements to the code from your JS Fiddle. You can view my edits at: https://jsfiddle.net/briankueck/u63maywa/
Some suggested improvements are:
Switching position:relative values in the JS layer to position:fixed in the CSS layer.
Shortening the jQuery DOM chains, so that the code doesn't start at the root element & walk all the way through the dom with each $ lookup. The scroller is now the root element. Everything uses .find() off of that element, which creates shorter trees & jQuery can traverse those branches faster.
Moving the logging code out of the DOM & into the console.log. I've added a debugging switch to disable it, as you're looking for the fastest scrolling on the table. If it runs fast enough for you, then you can always re-enable it to see it in the JSFiddle. If you really need to see that on the iPhone, then it can be added into the DOM. Although, it's probably not necessary to see the left & top position values in the iPhone.
Remove all extraneous $ values, which aren't mapped to the jQuery object. Something like $scroller gets confusing with $, as the latter is the jQuery library, but the former isn't.
Switching to ES6 syntax, by using let instead of var will make your code look more modern.
There is a new left calculation in the <th> tag, which you'll want to look at.
The iScroll event listener has been cleaned up. With position:fixed, the top <th> tags only need to have the top property applied to them. The left <td> tags only need to have the left property applied to them. The corner <th> needs to have both the top & left property applied to it.
Remove everything that's unnecessary, like the extraneous HTML tags which were used for logging purposes.
If you really want to go more vanilla, change out the .css() methods for the actual .style.left= -pos.left + 'px'; and .style.top= -pos.top + 'px'; properties in the JS code.
Try using a diff tool like WinMerge or Beyond Compare to compare the code from your version to what's in my edits, so that you can easily see the differences.
Hopefully, this will make the scrolling smoother, as the scroll event doesn't have to process anything that it doesn't need to do... like 5 full DOM traversing look-ups, rather than 3 short-tree searches.
Enjoy! :)
HTML:
<body>
<div id="wrapper">
<table id="scroller">
<thead>
</thead>
<tbody>
</tbody>
</table>
</div>
</body>
CSS:
/* ... only the relevant bits ... */
thead th {
background-color: #99a;
min-width: 120px;
height: 32px;
border: 1px solid #222;
position: fixed; /* New */
z-index: 9;
}
thead th:nth-child(1) {/*first cell in the header*/
border-left: 1px solid #222; /* New: Border fix */
border-right: 2px solid #222; /* New: Border fix */
position: fixed; /* New */
display: block; /*seperates the first cell in the header from the header*/
background-color: #88b;
z-index: 10;
}
JS:
// main code
let debug = false;
$(function(){
let scroller = $('#scroller');
let top = $('<tr/>');
for (var i = 0; i < 100; i++) {
let left = (i === 0) ? 0 : 1;
top.append('<th style="left:' + ((123*i)+left) + 'px;">'+ Math.random().toString(36).substring(7) +'</th>');
}
scroller.find('thead').append(top);
for (let i = 0; i < 100; i++) {
let row = $('<tr/>');
for (let j = 0; j < 100; j++) {
row.append('<td>'+ Math.random().toString(36).substring(7) +'</td>');
}
scroller.find('tbody').append(row);
}
if (debug) console.log('initialize iscroll');
let iScroll = null;
try {
iScroll = new IScroll('#wrapper', {
interactiveScrollbars: true,
scrollbars: true,
scrollX: true,
probeType: 3,
useTransition:false,
bounce:false
});
} catch(e) {
if (debug) console.error(e.name + ":" + e.message + "\n" + e.stack);
}
if (debug) console.log('initialized');
iScroll.on('scroll', function(){
let pos = scroller.position();
if (debug) console.log('pos.left=' + pos.left + ' pos.top=' + pos.top);
// code to hold first row and first column
scroller.find('th').css({top:-pos.top}); // Top Row
scroller.find('th:nth-child(1)').css({left:-pos.left}); // Corner
scroller.find('td:nth-child(1)').css({left:-pos.left}); // 1st Left Column
});
});
Is it necessary that you create your own scroller? Why don't you just style the data in HTML/CSS and just use the overflow attribute? JavaScript needs work on it's ability to adjust framerates. I was using your jFiddle earlier and it worked just fine with the native overflow handler.
Found this in the manual. Probably not what you wanna hear but it's the way it is:
IScroll is a class that needs to be initiated for each scrolling area. There's no limit to the number of iScrolls you can have in each page if not that imposed by the device CPU/Memory.
Try to keep the DOM as simple as possible. iScroll uses the hardware compositing layer but there's a limit to the elements the hardware can handle.
The reason the performance degradation is happening is that your scroll event handler is firing again and again and again instead of waiting for a reasonable and imperceptible interval.
The screenshot shows what happened when I tracked how many times the event handler fired, while scrolling for just a few seconds. The computationally-heavy event handler was fired over 600 times!!! This is more than 60 times per second!!!
It may seem counter-intuitive, but reducing the frequency that the table is updated will vastly increase perceived response times. If your user scrolls for fraction of a second, about 150 milliseconds, and the table is updated ten times, freezing the display during the scrolling, the net result is far worse than if the table were updated only three times and moved fluidly rather than freezing. It is just wasted processor burn to update more times than the browser can handle without freezing.
So, how do you make an event handler that fires at a maximum frequency, for example 25 times per second, even it is triggered much more often, like 100 times per second?
The naive way of doing it is to run a setInterval event. That is better, but horribly inefficient as well. There is a better way of doing it, by setting a delayed event handler, and clearing it on subsequent invocations before setting it again, until the minimum time interval has passed. This way it only runs no more often than at the maximum desired frequency. This is one major case for why the ``clearInterval'' method was invented.
Here is live working code:
https://jsfiddle.net/pgjvf7pb/7/
Note: when refreshing continuously like this, the header column may appear out of position.
I advise to do the update only when the scrolling has paused for about 25ms or so, rather than continuously. This way, it appears to the user that the header column is dynamically calculated as well as being fixed in place, because it appears instantly after scrolling rather than seeming to scroll with the data.
https://jsfiddle.net/5vcqv7nq/2/
The logic is like this:
variables outside your event handler
// stores the scrolling operation for a tiny delay to prevent redundancy
var fresh;
// stores time of last scrolling refresh
var lastfresh = new Date();
operations inside your event handler
// clears redundant scrolling operations before they are applied
if (fresh) clearTimeout(fresh);
var x = function() {
// stores new time of scrolling refresh
lastfresh = new Date();
// perform scrolling update operations here...
};
// refresh instantly if it is more than 50ms out of date
if (new Date() - lastfresh > 50) x();
// otherwise, pause for half of that time to avoid wasted runs
else fresh = setTimeout(x, 25);
Demo: https://jsfiddle.net/pgjvf7pb/7/
Once again, I recommend that you remove the line of code that refreshes the data instantly, and the else condition after that, and simply use one line
fresh = setTimeout(x, 25);
This will appear to instantly calculate the header column the moment any scrolling is finished, and saves even more operations. My second link to JS Fiddle shows what this looks like, here: https://jsfiddle.net/5vcqv7nq/2/
I have a routine that sizes elements in a page to fit snugly within their parent. In most cases, it is working admirably, but in Firefox (JUST Firefox - Chrome, IE, etc are fine) it is fumbling on the first attempt in one particular instance - a div nested within a fieldset fails to resize on the first attempt, but succeeds on the second (and subsequent) attempts.
Each element is sized relative to its parent using the following:
function resizeChild(elem) {
// Get gutter based on margins, borders, padding, etc
var gutter = getGutter(elem); // returns obj with x and y properties
var parent = elem.parentElement;
var parentStyles = window.computedStyle(parent);
var targetWidth = (parseInt(parentStyles['width']) - gutter.x;
var widthPx = targetWidth + 'px';
// prototype.js setStyle shortcut
elem.setStyle({
width: widthPx,
maxWidth: widthPx,
minWidth: widthPx
});
}
I run this in a loop, iterating over every element with a particular CSS class.
According to the Firefox debugger, the outer element (the fieldset) is always being resized before the inner div. I can inspect the element, and see the style attributes being set appropriately. However, on the next iteration of the loop, when the parent is being evaluated (I can see in the javascript property inspector that the parent is indeed the fieldset), the value for width that is returned for the computed style is the previous, unmodified value, thus the inner div is resized incorrectly.
Can somebody shed some light on this please?
Edits after comments:
parent.clientWidth returns 0.
Not sure if this is relevant, but a parent div of the fieldset had display set to none shortly prior the resize operation being called. However, at the point at which the fieldset was resized, the display of the div was set to inline-block. I don't think this would make a difference, but then I'm not well educated on some of the particular behaviours of Firefox in this scenario.
I found a solution to this, although it's a little situational.
It seems that if the width of the parent element has been dynamically modified using prototype.js#Element.setStyle() (and, for all I know, other libraries that directly modify the style attribute), then the computedStyle() method won't reflect the change until all changes have completed.
The solution was to check to see if the parent element of the element being resized also had the CSS class that flagged the elements for resize, and if it did, get the size from the style attribute instead of using computedStyle(). Here's the full function, with modifications:
function resizeFullwidth() {
$$('*.fullWidth').each(function(elem, i) {
// Get gutter based on margins, borders, padding, etc
var gutter = getGutter(elem); // returns obj with x and y properties
var parent = elem.parentElement;
var parentStyles = (
parent.hasClassName('fullWidth')
? window.computedStyle(parent)
: parent.style);
var targetWidth = (parseInt(parentStyles['width']) - gutter.x;
var widthPx = targetWidth + 'px';
// prototype.js setStyle shortcut
elem.setStyle({
width: widthPx,
maxWidth: widthPx,
minWidth: widthPx
});
});
}
This now works correctly in all browsers :)
Thanks very much for your help, people :)
Have you tried var targetWidth = parent.clientWidth ?
See : MDN Element.clientWidth
I monitored the page load time for my site in chrome (using Timeline), and got hundreds of warnings that said "Forced synchronous layout is a possible performance bottleneck" for all the Raphael.js calls:
I don't know how to fix this :/
Any ideas on where to look?
Here's how I normally create text on the page:
currentPaper.text(width,height,"text")
.attr({opacity:.8, fill: "#333", 'font-size':10})
.id = "someId";
//Or, to be exact:
currentPaper.text(x_pos+(width/2),
y_pos-height/8,
lopaLayoutArray.x[s].y1[r].y2[x])
.attr({opacity:.8, fill: "#333", 'font-size':(10*size)})
.id = seatName+"-textLabel";
Thanks!
Problem:
The problem is called "Layout Thrashing" and is happening because the browser was trying to immediately refresh / redraw after every Raphael drawing.
For example, this would cause layout thrashing:
var a = document.getElementById('element-a');
var b = document.getElementById('element-b');
a.clientWidth = 100;
var aWidth = a.clientWidth;
b.clientWidth = 200;
var bWidth = b.clientWidth;
"In this simple example, the browser will update the whole layout twice. This is because after setting the first element's width, you are asking to retrieve an element's width. When retrieving the css property, the browser know's it needs updated data, so it then goes and updates the whole DOM tree in memory. Only then, it will continue to the next line, which will soon after cause another update because of the same reasons."
This can simply be fixed by changing around the order of the code, as so :
var a = document.getElementById('element-a');
var b = document.getElementById('element-b');
a.clientWidth = 100;
b.clientWidth = 200;
var aWidth = a.clientWidth;
var bWidth = b.clientWidth;
"Now, the browser will update both properties one after the other without updating the tree."
Sources:
https://blog.idrsolutions.com/2014/08/beware-javascript-layout-thrashing/
http://www.codeproject.com/Articles/758790/Debugging-and-solving-the-Forced-Synchronous-Layou
http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/
https://dev.opera.com/articles/efficient-javascript/?page=3#reflow
Soultion:
One way to fix this (a really quick and dirty way) is to hide() the div you are drawing to, then, when you are finished, show() it. I have had hit and miss results with this with Raphael, and it has been drawing strange this way. However it is faster...
"When an element has its display style set to none, it will not need to repaint, even if its contents are changed, since it is not being displayed. This can be used as an advantage. If several changes need to be made to an element or its contents, and it is not possible to combine these changes into a single repaint, the element can be set to display:none, the changes can be made, then the element can be set back to its normal display."
Here's the results:
This way worked for me!
I am building a lightbox style div element for an ibooks epub. I want the div to be displayed on the current page being viewed at the time. If the image is on page two of the ebook, I want the lightbox to showup on page two. I have the div width and height set to fill the screen.
document.getElementById("LightBoxDiv").style.width=window.innerWidth+"px";
document.getElementById("LightBoxDiv").style.height=window.innerHeight+"px";
I can manualy set a fixed top value of the div if I know which page number an image is on. My device has a 460px height on the window. So for an image on page two, the top should then be 460 which is the beginning of the 2nd page.
document.getElementById("LightBoxDiv").style.top="460px";
However, as ebooks are dynamic in that the user can change the size of the text larger or smaller, the page upon which something might fall changes. I need a way to set the top dynamically based upon the current page. If I know the current page number being viewed, I can set the div top to
var lighboxHeight = (pagenumber-1)*window.innerHeight;
I tried using the window.pageYOffset to calculate the current page, but this always gives a 0 value as the page does not scroll in an ebook. Unfortunately, I can find no documentation or any reference describing how to use javascript to access the page numbers. Does anyone have any idea how to access or find the current page number in an ibooks epub using javascript?
Thanks,--christopher
I believe I found the solution. This question/answer helped a lot.
//window height
var winHeight = window.innerHeight;
//top of object to be placed in the lightbox
//can be any object
var curOjbTop = document.getElementById(svgId).getBoundingClientRect().top;
//body y value
var bodyTop = document.body.getBoundingClientRect().top;
//amount the object is shifted vertically downward from the top of the body element
var offset = curObjTop - bodyTop;
//page number of the object
//this is actually 1 less than the true page number
//it basically starts the page count at 0, but for actual page number add 1
var curPage = Math.floor(offset/winHeight);
//this sets the top edge of the lightbox at y=0 on the page the item is on
var lightboxTop = curPage*winHeight;
document.getElementById("LightBoxDiv").style.top=lightboxTop;
My lightbox div covers the entire viewing area, but if you wanted a smaller one that was centered, you would need to add an additional half of the window height and then set the top margin to be half the negative amount of the height you wanted.
For example if the light box was 200 x 200, then your lightboxtop would be
var lightboxTop = (curpage*winHeight)+(winHeight*.5);
var topMargin = "-100px";
It may need to be tweeked some, but overall it should work to determine a page number.
I need to split some html content to pages, so that each page would have height of the screen and some predefined width. Page split can happen in the middle of paragraph (or probably some other html element), so this situation should be handled somehow.
What I really want to achieve is the effect of reading the book, page by page. I assume there will be a need for some javascript, so I'd prefer to to this with jQuery, but if other framework is required, it's also okay.
I have to admit that I'm quite new to HTML and all, so sorry if my guess is stupid, but currently I'm considering the following approach: measure actual height of the visible area (need to figure out how yet), then take my html document and incrementally take tag after tag, put this into invisible div and calculate its resulting height. When I'll have its height more than page height, I'm done. However, this approach will not work in case of long tags, e.g. long paragraph.
Thanks in advance.
EDIT: thanks for your previous answers. I tried to use approach of manual calculating the size of the elements, and encountered one problem which I cannot solve in a good way. This is problem of collapsing margins. What I'm trying to do is to loop through all the paragraphs in my document and sum up results of .outerHeight(true) jQuery call. This should give me the full height of element, including padding, margin and border. And it actually does what it says, but the problem here is that it doesn't take collapsing margins into account. Because of that I end up with wrong overall size (bigger than real one), because browser throws away some of margins (of adjacent paragraphs in my case), but I take them into account.
Any ideas how to solve this other than introducing the algorithm deciding which margins are collapsed and which are not? I think it is ugly...
You could use CSS3 multi-column rules, example: http://www.quirksmode.org/css/multicolumn.html
Or for support in all browsers use a javascript plugin: http://welcome.totheinter.net/columnizer-jquery-plugin/
This plugin even has a multi-page multi-column example: http://welcome.totheinter.net/2009/06/18/dynamic-multi-page-multi-column-newsletter-layout-with-columnizer/
I can think of one framework which seems to do what you need (and a bit more): https://github.com/Treesaver/treesaver
jQuery will give you the height (in pixels) of an element with the height() function.
jQuery("BODY").height() will give you the maximum height of the view port (though only if your content height is >= the height of the BODY - otherwise it will give you the height of how much space the body is taking up in the view port.)
Counting the heights of the P tags (or other tags) seems like a good way to go. I suppose if you want to break up the content of a P tag for large paragraphs, you could define a maximum "breakage" height for the last P tag on a page. You can then break rest of the contents of the P tag by creating a new P tag with jQuery("#the-next-page-id).append(jQuery("<P>"+restOfTheParagraphContent+"</P>"))
Use your own logic to calculate the height of each element in the html body
using jQuery code
$('selector').height();
Using this, you can calculate the height of some html elements and decide how much
elements should be displayed on your device screen.
for more, please visit jQuery Height Documentation
In case anyone still looking for something like this I recently did it using JQuery. It also leaves the first page empty (for title and such):
https://jsfiddle.net/xs31xzvt/
It basically iterates over the movable items and insert them into a new div if the previous div is full.
(function($) {
$(document).ready(formatPages)
function formatPages() {
var container = $('.container');
var subPage = $('.subpage').get(0);
var subPageHeight = subPage.offsetHeight;
var subPageScrollHeight = subPage.scrollHeight;
// See how many pages we'll need
var pages = Math.floor(subPageScrollHeight / subPageHeight) + 1;
//add a new page
var pageCount = 2;
var pageDiv = createPageDiv(pageCount);
var subPageDiv = createSubPageDiv(pageCount);
var addPage = function (){
pageCount++;
pageDiv = createPageDiv(pageCount);
subPageDiv = createSubPageDiv(pageCount);
pageDiv.append(subPageDiv);
container.append(pageDiv);
pageContentHeight = 0;
}
addPage()
container.append(pageDiv);
$('.movable').each(function() {
var element = $(this);
//remove the element
element.detach();
//try to add the element to the current page
if (pageContentHeight + element.get(0).offsetHeight > subPageHeight) {
subPageDiv.append(getFooterDiv().show());
//add a new page
addPage();
}
subPageDiv.append(element);
pageContentHeight += element.get(0).offsetHeight;
});
}
function createPageDiv(pageNum) {
return $('<div/>', {
class: 'page',
id: 'page' + pageNum
});
}
function createSubPageDiv(pageNum) {
return $('<div/>', {
class: 'subpage',
id: 'subpage' + pageNum
});
}
function getFooterDiv() {
return $('.footer').first().clone();
}
}(jQuery));