Absolutely positioned parallax elements cause page height to be too large - javascript

EDIT: Here is another fiddle, this one showing the problem exactly as it is occurring. In my eyes, since the section bg is INSIDE of the container, overflow hidden should be hiding it: http://jsfiddle.net/9SPDq/2/
I've implemented a custom parallax scrolling solution for my client's site. I've included relevant snippets at the bottom of the question. Since the site is a complete Ajax piece, the background must be as high as the highest page, which in this case is the homepage. When looking at the homepage, all works as expected because all the pieces are arranged to fit a page of that height. But when the user navigates to another page which is significantly shorter, the problem arises: the user can scroll 10x the height of the page because the <html> thinks it has to scroll to the parallax pieces, when in fact I only want it scrolling to the contents of <body_container>. I have tried various combinations of overflow:scroll and :hidden, to no avail.
Here is a js fiddle of what I imagine SHOULD happen, but it seems that because in that example what is #parent is <html> in the production site and the #container is <body>, something is breaking. Namely, because the way I've done it #parent requires overflow:hidden, it removes all scroll bars in the production site because I'm making <html> have overflow:hidden.
http://jsfiddle.net/vb5cp/
The blue <section>, in my eyes, is happily absolutely positioned outside of the scroll area, and for that reason should not affect the height of the #container.
Since the production site's beta is up, I'll provide a link to one of the pages exhibiting the problem, too, in case my descriptions haven't been clear: http://beta.firedogcreative.com/#motion_design
The question is: how do I retain the parallax background pieces, but control the height of my pages so that they only scroll to the height of the page contents?
To summarize the implementation:
<body>
//here are the parallax pieces, with various hooks for the js that control them
<section id="bckgrnd1"></section>
<section id="bckgrnd2" data-type="background" data-speed="6"></section>
<section id="bckgrnd3" data-type="background" data-speed="5"></section>
<div id="body_container">
//actual page content
</div
</body>
Each piece has some simple css, the key part being the absolute positioning:
#bckgrnd2 {
background: url(/media/images/parallax_pieces/smalltopFlame.png);
background-repeat: no-repeat;
background-size: auto;
height: 410px;
width: 661px;
margin-left: 215px;
position: absolute;
}
And finally the js that does all the work:
$(document).ready(function(){
$("html").css("background-image","none");
$("section").show();
$('section[data-type="background"]').each(function(){
var $bgobj = $(this); // assigning the object
$(window).scroll(function() {
var yPos = -($(window).scrollTop() / $bgobj.data('speed'));
// Put together our final background position
var coords = yPos + 'px';
// Move the background
$bgobj.css({ top: coords });
});
});
});
// Create HTML5 elements for IE
document.createElement("article");
document.createElement("section");

To those who could follow my meandering and convoluted question, and who are having a similar problem, here is what was going on.
The <sections>, since they were being used as background image containers for a parallax scroll, were absolutely positioned and thus outside the flow. Since their container was <body>, they were treated as a part of the page, and thus even though overflow:hidden was set on <body>, they weren't being viewed as overflow and thus the user could scroll too far down the page. The solution was to contain all the <section>'s in a single div, and then set the height of THAT div in ADDITION to <body>. The result is exactly what you would expect: the user can only scroll down as far as the set height, exactly as you would want.

Related

Display dynamic html content like an epub/ebook, without converting html to epub format?

I want to create a responsive, mobile optimized reading experience similar to an epub/ebook reader, like the Kindle app, or iBooks, using dynamic html as the source.
Imagine a long article or blog post that requires a lot of vertical scrolling to read, especially on a small mobile device. What I would like to do is break the long page into multiple full-screen sections, allowing the user to use left/right navigation arrows and/or the swipe gesture to "page" through the article.
There are many JS libraries available that can create a "slide show" or "carrousel" of pre-defined slides (using divs or other container elements). But I want the text and html content to dynamically re-flow to fit any device viewport and still be readable... just like an epub/ebook user interface, like the Kindle app or iBooks. So, for the same article, there would be many more "pages" on a phone than there would be on a tablet or desktop viewport, and those "pages" would need to be dynamically created/adjusted if/when the viewport size changes (like switching from portrait to landscape on a mobile device).
Here is an example of a javascript .epub reader: epub.js
... notice the responsive behavior. When you resize your viewport, all the text re-flows to fit the available space, increasing or decreasing the total number of "pages". The problem is that epub.js requires an .epub file as its source.
What I want is the same user interface and functionality for an html page.
I have searched and searched for some kind of library that can do this out of the box, but haven't been able to find anything.
I realize that I could use a conversion script to convert my html page into an .epub file, and then use epub.js to render that file within the browser, but that seems very round-about and clunky. It would be so much better to mimic or simulate the .epub reader user experience with html as the direct source, rendering/mimicking a client side responsive ebook user experience.
Does anyone know if something like this already exists, or how I could go about building it myself?
The crucial functionality is the dynamic/responsive text-reflow. When the viewport dimensions are reduced, the text/content needs to reflow to the next "page" to avoid any need for vertical scrolling. I don't know how to do this efficiently. If I were to code it myself, I might use something like the jQuery Columnize plugin, setting all columns to width: 100vw; height: 100vh, so that each column is like a "page", and then figuring out how to create a swipe UI between those "pages".
Any help is much appreciated!
This becomes very difficult if the html page is complex, eg with precisely positioned elements or images. However if (as in the epub.js example) the content consists only of headings and paragraphs, it is achievable.
The basic idea is to progressively add content until just before the page overflows. By keeping track of where we start and stop adding content, clicking to the next page is a case of changing the page start to the previous page end (or vice versa if you're going back).
Process for reshaping content into pages
Let's assume you have all your content in one long string. Begin by splitting all the content into an array of words and tags. It's not as easy as splitting by whitespace as whitespace between < and > should be ignored (you want to keep classnames etc within each tag). Also tags should be separated as well, even if there is no whitespace between the tag and a word.
Next you need a function that checks if an element's contents overflow the element. This question has a copy-paste solution.
You need two variables, pageStart and pageEnd, to keep track of what indexes in the array are the beginning and end of the current page.
Beginning at the index in pageStart you add elements from the array as content to the page, checking after each add whether or not the contents overflow. When they do overflow you take the index you're up to, minus 1, as the index for pageEnd.
Keeping tags across page breaks
Now if all's ticketyboo then this should fill the page pretty well. When you want to go to the next page set your new pageStart as pageEnd + 1 and repeat the process. However there are some issues that you may want to fix.
Firstly, what happens if the page overflows in the middle of a paragraph? Strictly speaking the closing tag, </p>, is not required in HTML, so we don't need to worry about it. But what about the start of the next page? It will be missing an opening tag and that is a major problem. So we have make sure we check if the page's content begins with a tag, and if it doesn't then we get the closest opening tag prior to the current pageStart (just step back along the array from pageStart) and add it in before the rest of the content.
Secondly, as shown in the example, if a paragraph continues onto the next page, the last line of the current page is still justified. You need to check if pageEnd is in the middle of a paragraph and if so add syle="text-align-last:justify;" to the opening tag of that paragraph.
Example implementation
A pen showing all this in action is at https://codepen.io/anon/pen/ZMJMZZ
The HTML page contains all content in one long element. The content is taken directly from the container #page and reformed into pages, depending on the size of #page. I have't implemented justifying the last line if a page break occurs within a paragraph. Resize the #page element in the css and see how the content resizes itself - note that since the page size is fixed you'll have to use click forward and back to trigger a recalculation. Once you bind the page size to the window size, recalculating pages on the fly simply involves adding a resize event listener to the window that calls fillPage.
No doubt there are numerous bugs, indeed it will sometimes display things incorrectly (eg skipping or repeating words at the beginning or end of a page), but this should give you an idea of where to start.
Take a look at this repository on GitHub. Otherwise, you can create a one-page website with many sections, each one as high as the viewport, by using only CSS (demo):
.section { height: 100vh; }
or by using JavaScript, adding an anchor to each section to navigate between them, and applying a responsive unit (my demo) for the text of each section, to adapt it on resize... Something like this:
var curr_el_index = 0;
var els_length = $(".container").length;
$(".next_section").on("click", function(e) {
curr_el_index++;
if (curr_el_index >= els_length) {
curr_el_index = 0;
}
$("html, body").animate({
scrollTop: $(".container").eq(curr_el_index).offset().top
}, 300);
return false;
});
$(".previous_section").on("click", function(e) {
curr_el_index--;
if (curr_el_index < 0) {
curr_el_index = els_length - 1;
}
$("html, body").animate({
scrollTop: $(".container").eq(curr_el_index).offset().top
}, 300);
return false;
});
* {
border: 0;
margin: 0;
padding: 0;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
body {
background-color: #1a1a1a;
}
section {
height: 100vh;
background-color: #eee;
border: 2px solid red;
font-size: 6vw;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="container">Section 1 Previous Next</section>
<section class="container">Section 2 Previous Next</section>
<section class="container">Section 3 Previous Next</section>
<section class="container">Section 4 Previous Next</section>
<section class="container">Section 5 Previous Next</section>
EDIT #1
An idea of algorithm, that come from a my codepen, that uses the same jQuery plugin:
Create your reader layout, copying the whole text in it
Use this jQuery plugin to check the text inside the viewport (demo)
Count the number of characters/WORDS with "Onscreen" label in the
viewport (a references)
Split the whole text in a list containing as many characters/WORDS as
there are in the "Onscreen" label
Create a section for each element of the obtained list, filling each
section with the relative text; the number of elements of the list
gives you the number of pages (sections) of the whole text. You may
navigate between sections like above
On resize event, redo [2-5] algorithm steps
Cheers
The idea is to have a div that will contain the whole text (let's call this div #epub_container). Then, you will have a div with the same size of the page viewport (let's call it #displayer) and it will contain #epub_container.
#displayer will have css overflow:hidden. So when the site loads, it will only show the first page, because the rest of the #epub_container will be hidden.
Then you need a page navigator to increment/decrement the page number. When the page number changes, we will move the top offset of the #epub_container based on that.
This is the jQuery function:
function move_to_page() {
var height = window.innerHeight;
var width = window.innerWidth;
var $displayer = $('#displayer');
var offset = $displayer.offset();
$displayer.height(height - offset.top - 5);
var $epub = $('#epub_container');
var offset_top = offset.top - $displayer.height() * m_page;
$epub.offset({top: offset_top, left: offset.left});
}
JSFiddle
EDIT: call move_to_page() after the text reflow in order to recompute the pages.
I created a plugin that handles this perfectly. It has features like dark mode, font changing, line height adjustment, select chapter in a side nav menu, save and restore scrolling/reading position. You can find it for free on git hub at https://github.com/E-TechDev/Html-Book-Reader
Screenshots
Light Mode Dark Mode Side Nav Menu Change Font Adjust Paragraph
You can try CSS scroll snap points on text with columns
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap_Points
Somehow make the columns as wide as the viewport, and allow horizontal snapped scrolling.
Update
I mean to try to do the text flowing entirely using css. Pen:
https://codepen.io/ericc3141/pen/RYZEpr
body {
scroll-snap-type: mandatory;
scroll-snap-points-x: repeat(100%);
}
#columns-test {
height: 80vh;
columns: 90vw auto;
}
All you need is to insert a page break at the right places when you load the page. You can take the cue from here:
Dynamic Page-Break - Jquery
Here you can set the page height to your viewport height. You can handle the rest with javascript and css.

Fixed placed element within certain area

I have a page where I display a long list of results from a DB query.. and I also show a Google Map to the RIGHT of this long list.
Map is roughly 240px wide and maybe 600px long/height.
This MAP is inside a container DIV (#mapContainer).. that contains the map, and a dropdown box above the map canvas.
Currently, the mapContainer scrolls along with the page itself.. what I would like to do is have it be static/fixed element. So it starts/displays/is placed where I have it currently on the page.... if I scroll the page.. the map should stay fixed.. until the end (bottom) of the results are scrolled to..
(I dont want the mapContainer to scroll and cover the footer element/div)
Following this tutorial:
http://www.webgeekly.com/tutorials/jquery/a-simple-guide-to-making-a-div-static-as-you-scroll-past-it/
It doesnt stay fixed..
//sticky map placement
$(function () {
var msie6 = $.browser == 'msie' && $.browser.version < 7;
if (!msie6) {
console.log("NOT IE 6");
var top = $('#mapContainer').offset().top;
$(window).scroll(function (event) {
console.log("scrolling.......");
var y = $(this).scrollTop();
if (y >= top) {
$('#mapContainer').addClass('fixed');
console.log("class added");
}else {
$('#mapContainer').removeClass('fixed');
console.log("class removed");
}
});
}
});
The first console.log() outputs fine.. but nothign in the window.scroll() portion fires ever.
Rest of code used:
#mapContainer{
display:table;
width:240px;
float:right;
/* sticky map */
position: absolute;
top: 458px;
left: 50%;
/* width: 100px; */
margin-left: 339px;
}
#<span class="skimlinks-unlinked">mapContainer.fixed</span> {
position: fixed;
}
On the tutorials page itself.. he has a toolbar on the left side..
that stops 'being fixed' when you scroll all the way to the top.. (it will start to move with the rest of the page scroll at a certain point).. and it doesnt go all the way down to cover the footer either.
I'm not clear why the jQuery portion isnt firing.. and I'm not clear what that last style is for? (seems odd looking)
All this absolute, fixed, relative, to parent, to viewport..etc.. is confusing.
Any easy to read/follow/understand tutorials that will get me to where I want to be? Or suggestions on what I am doing wrong with the correct approach?
I looked at your Fiddle and noticed a couple things:
Your "fixed" class was not represented in the CSS. When I looked into the CSS I saw a span element wrapping a ".fixed" reference with a position property set.
You are styling the mapContainer div using the ID. This is a very rigid selector as the order of CSS selectors goes. The hierarchy of CSS selectors is specifid and IDs will override types and classes. See: http://htmlhelp.com/reference/css/structure.html
The when scrolling, I am seeing the console logging in my dev tools. Also, when inspecting the element, I am seeing it add and remove the class name.
Based on my observations, modifying the CSS selector for your container should do the trick. Adding the ID to the class will keep the CSS rule specific enough:
#mapContainer.fixed { position: fixed; }
Refer to this updated Fiddle for an example with these changes in place:
http://jsfiddle.net/pmurst8e/4/
Update: For demonstration purposes of what I was referring to with the resize I modified your example a bit. It isn't the prettiest, but it conveys the point: http://jsfiddle.net/pmurst8e/6/
Update: There are a couple issues with the latest Fiddle (v12):
The sidebar will always go fixed the moment you scroll because "top" is never calculated. It's being set to zero, and the offset calculation is commented out.
Absolute positioning is relative to the closest positioned parent. If no parent is positioned, it's relative to the window. To constrain an absolute positioned element, set the constraining parent to "position:relative;".
Instead of using a percentage and left position rule, consider positioning the sidebar to the right, relative to the "contentContainer", by a set number of pixels.
When the fixed position takes effect, we also need to set the sidebar fixed left position. Otherwise, it will use the positioning in the CSS. In contrast to absolute positioning, Fixed positioning is relative to the window, meaning an absolute element "right: 10px" will be 10px from the right of the positioned parent, but will appear 10px from the right of the window when fixed.
You don't need a float when you have absolute positioning. Absolute position removes an element from the normal flow of the document, and because of this float does not apply.
I updated the Fiddle to show how to make these adjustments. I cleaned out the float and margin from the mapContainer and left the absolute positioning. With that I set the contentContainer to relative to constrain mapContainer to it. You will also see, on the script side, I added a line to set the offset of mapContainer. Without this, when it becomes fixed it will be 10px off the right border of the window.
Updated Fiddle: http://jsfiddle.net/pmurst8e/14/
Also, you want to leave your top offset line in tact. Without that, it goes fixed the moment the scroll moves and never goes back. When that becomes the case, you're better off just setting fixed permanently.
var top = $('#mapContainer').offset().top; // you want this
Regarding the bottom boundary, you can do a couple things:
Resize the sidebar so that it shrinks to the window size. This is demoed in my example from my first post in this and the downside is it forces the sidebar to become a scrollable div so the child content is all visible.
Use a check for the bottom so that when you hit the limit, the container goes back to an absolute position, but one set at the bottom: 0 of the parent.
Something like:
var limit = $('footer').offset().top;
var $mc = $('#mapContainer');
var pos = $mc.offset().top + $mc.outerHeight();
if (pos >= limit) {
$mc.removeClass('fixed')
.addClass('bottom-set').css('left',''); // define this in CSS for bottom absoluteness
}
#mapContainer.bottomFixed {
bottom: 0;
top: auto;
}
And to be fully honest, you might save yourself some time working this all out if you take a look at the ScrollToFixed plug-in (https://github.com/bigspotteddog/ScrollToFixed). I seem to be mentioning it quite a bit lately, but this issue seems to be a popular one right now.
Incidentally, go to your OP and click the Edit button. Shrink the height of your browser and scroll down. You should see SO has a fixed sidebar that passed the footer. ;)

Can I expand a div so the overflow-y:auto scrollbar doesn't clip the div content?

Similar question, without a great answer:
How can I include the width of "overflow: auto;" scrollbars in a dynamically sized absolute div?
I have a <div> of fixed height that acts as a menu of buttons of uniform width. Users can add/remove buttons from the menu. When there are more buttons than can fit vertically in the <div>, I want it to become scrollable - so I'm using overflow-y:auto, which indeed adds a scrollbar when the content is too large in y. Unfortunately, when the scrollbar shows up it overlaps the menu buttons, and adds a horizontal scroll bar as a result - the big problem is it just looks horrible.
Is there a "right" way to fix this? I'd love to learn some style trick to make it work right (i.e. the scrollbar sits outside the div rather than inside, or the div automatically expands to accommodate the scroll bar when necessary). If javascript is necessary, that's fine - I'm already using jQuery - in that case, what are the right events are to detect the scrollbar being added/removed, and how do I make sure to use the correct width in a cross-browser/cross-style way?
JSFiddle: http://jsfiddle.net/vAsdJ/
HTML:
<button type="button" id="add">Add a button!</button>
<div id="menu">
</div>
CSS:
#menu{
background:grey;
height:150px;
overflow-y:auto;
float:left;
}
Script:
$('#add').button().click(function(){
var d = $('<div/>');
var b = $('<button type="button">Test</button>');
d.appendTo($('#menu'));
b.button().appendTo(d);
});
First: To remove the horizontal scrollbar set overflow-x: hidden; as Trent Stewart has already mentioned in another answer.
CSS Approach:
One thing I have done in the past is to add a wider wrapping div around the content div to make room for the scrollbar. This, however, only works if your container width is fixed... and may need to be adjusted (by serving different styles) in various browsers due to variable rendering of scrollbars.
Here a jsfiddle for your case. Note the new wrapper <div id="menu-wrap"> and its fixed width width: 95px;. In this case the wrapper div is doing the scrolling.
You could probably also solve this by giving the wrapper some padding on the right, and thereby avoid the fixed width problem.
jQuery Approach:
Another option is to detect the overflow using jquery as described here, and then increasing the width or padding of the div to make space. You may still have to make browser-specific adjustments though.
Here a jsfiddle with a simplified version for your example. This uses your click function to check the div height after every click, and then adds some padding to make room for the scrollbar; a basic comparison between innerHeight and scrollHeight:
if($('#menu').innerHeight() < $('#menu')[0].scrollHeight){
$('#menu').css( "padding", "0 15px 0 0" );
}
To make this more cross-browser friendly you could check for the scrollbar width (as outlined here) and then add the returned value instead of the fixed padding. Here another jsfiddle to demonstrate.
There are probably many other methods, but this is how I would go about it.
Have you tried simply using overflow-x: visible; or hidden

A DIV's style width:"100%" seemingly causes a scroll bar due to lack of fix-width parent DIV

As asked and explained in this question, the problem with the DIV's width set to 100% is that it'll get the window's width and not the enclosed BODY element.
The solution suggested is to place an auxiliary DIV between BODY and the actual DIV and make its width fix. But that just puts the issue to the next level, doesn't it?
Since I don't know the screen size of my users' viewers (let's call it platform independence - a term I've heard somewhere is good to keep in mind when developing for the web, hehe), I need the main-all-mighty-rooted-and-parentest DIV to be filling out all available space without sticking out.
Of course, setting fix width on BODY won't work. Should I go ugly and pull the width of the part of the window that isn't the window, double it (once for each side) and retract that to set the fix width of some root DIV element?!
And if so - how?! I'm unclear on how to obtain the magic width (which, however, might be googleable) but mostly I'm unsure how to enter that parameter into the static CSS file. Will I have to do that dynamically using jQuery and ready function?
Edit
I executed this line from the console.
$("body")[0].outerHTML
The result was as follows - still displaying the scroll bar.
<body style="width: 600px;">
<div id="mapDiv" style="width: 100%; height: 100%; position: absolute;">!</div>
</body>
Then I executed this line.
$("#mapDiv")[0].style.position = ""
Poof, the scroll bar is gone. I thought absolute was the default setting... Apparently it isn't. There's the problem.
Based on your edit if you have that code then you are stretching your body tag to 600px and the <div> inside with absolute position and width 100%.
First the default for position is static.
Since you are using absolute position this happen, is taken off the DOM and search for a new containing block:
The containing block for a positioned box is established by the nearest positioned ancestor
If you don't set the body with any position value then the div is off and takes values in relation to another parent in this case is the window or <html> tag.
Then if you inspect the element is with the dimensions of html tag but positioned where he was in this example http://jsfiddle.net/wZ57C/. Is causing scroll because has 100% dimensions but positioned where body begins wich is at margin 8px aprox. Here you solve the scroll just adding position top:0 left:0 check here http://jsfiddle.net/wZ57C/1/.
But if you want the div be 100% of the body and position:absolute then make the body the relative parent http://jsfiddle.net/wZ57C/2/.

Set an element height via CSS or JavaScript/JQuery

This should be simple but nothing's working:
Question
How do you set the height of a webpage to be, lets say, exactly 4000 pixels—in such a way that scroll bars exist even when the page is blank?
Background
I'm new to JavaScript/JQuery but very experienced with similar technologies. I'm trying to do some fancy effects based on scrolling the page. To accomplish this methodically, as a first step I'm looking to make a "really tall" page. From there I will hide/display items based on the scroll height with pseudo-code along the lines of:
function onScrollEvent() {
var height = scroll height
var sectionIndex = Math.floor(height / MAX_SECTION_HEIGHT);
for each item in my array of graphics
if item index != sectionIndex then item.fadeOut else item.fadeIn
}
Once I have that working, I'll start creating the effects I want to see. The problem is, I can't make the stupid page "really tall."
Summary
When I set the height style property of the main-content div, it doesn't seem to trigger scroll bars unless there's actual content on the page. How do I make the page "permanently tall," so to speak? That is, I want the page to behave (scroll) as though it has 4000 pixels of content even if there's only one line of text on the page. Right now it behaves as though there's a call to:
height = Math.min(height of contents, height of div style)
Have you tried min-height for body, or html tags? min-height requires the element to be at least that height regardless of the content contained.
CSS
html, body{
min-height: 4000px;
}
Live Demo
Reference
Easy in CSS:
body
{
height: 4000px;
}
Example here.
This is the simplest way. min-height is not supported by all browsers. This is a specific height that you can set to the body tag (essentially the webpage itself) to make it really tall.
In your CSS add:
body
{
min-height: 4000px;
}
And you'll also need:
body
{
height: 4000px;
}
for internet explorer (via IE's conditional comments).
In Chrome 10, on OSX 10.6 -- this renders a complete blank page with scroll on the Y axis, hope this is how you meant:
http://pastie.org/1674432

Categories

Resources