Parallax - Element Glitching - javascript

So I've been trying to wrap my head around this neat effect called Parallax. Where basically the background scrolls slower than the foreground elements.
I've found this new "trick" which is working. Changing the top property to create the parallax effect, as the scrolling goes.
The issue...
So, for performance purposes and lifting the stress from the CPU when the element is not inside the user's viewport, I've created an if statement, which checks if the top position is more than 300px. If it is, it overwrites everything and sets the top property back to 0, so it won't keep increasing it for no reason.
Now, just scroll for a bit. See how, as the red div comes over the white one, the white one stutters? Looking in the DOM inspector, I see that the if statement is freaking out, setting the top property to 0px even if it's not more than 300px. Halp.
While we're at it, I'd love to see more suggestions regarding parallax effects. I've seen a few answers already regarding this effect, but they seem... overly complicated for me. I know there are better ways to do this, I know there are.
And also, It would be greatly appreciated if there were no jQuery answers. Thanks.
var txtfirst = document.getElementById("txtfirst");
window.onscroll = function(){
var ypos = window.pageYOffset;
txtfirst.style.top = ypos * 0.4 + "px";
if(txtfirst.style.top > '300px'){
txtfirst.style.top = '0px';
}
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.text-first {
display: flex;
text-align: center;
justify-content: center;
align-items: center;
font-size: 32px;
font-family: Arial;
color: gray;
width: 100%;
height: 500px;
position: relative;
}
.foreground-red {
width: 100%;
height: 600px;
background-color: red;
display: flex;
justify-content: center;
align-items: center;
font-family: Arial;
color: gray;
font-size: 32px;
}
.spacer { /*for scrolling purposes*/
width: 100%;
height: 1000px;
}
<div class="text-first" id="txtfirst">THIS IS SOME TEXT</div>
<div class="foreground-red">THIS SHOULD GO ABOVE</div>
<div class="spacer"></div>

Your application most likely (you only provided selected snippets, I assume) does not work at least because you are comparing text when you want to be comparing numbers, in your attempt at optimization:
txtfirst.style.top > '300px'
The above will not behave like what you'd expect it to. Every property of the Element::style property (e.g. txtfirst.style in your case) is a text string, not a number. A test like "50px" < "300px" does not compare whether 50 is less than 300, it compares the text values lexicographically.
If you actually want to compare the amount of pixels, you can use parseInt function to convert a value like 50px to a number, 50. Your test will then look as follows:
parseInt(txtfirst.style.top) < 300
Now, what follows is a number of problems with your approach to solving this and suggested solutions, since you are interested in suggestions.
Using inline styles is problematic in general (subjective)
Inline styles have the highest precedence in CSS, which can be problematic in cases where the user has their own style sheets, as properties set in those will be ignored in favor of properties set inline.
Reading properties of inline style back assuming that would be the actual used value, is just plain wrong. Inline style object tracks assigned values, not computed or used values. The Window::getComputedStyle(element) function, on the other hand, retrieves computed style for the element.
Solution? Reading properties using getComputedStyle and writing them directly to a preferred (or empty, if so desired) stylesheet (document.styleSheets, reflecting all link rel=stylesheet and style elements):
function rule(selector) {
var sheet = document.styleSheets[0];
return Array.prototype.find.call(sheet.cssRules, rule => rule.selectorText == selector);
}
var txtfirst = document.getElementById("txtfirst");
window.onscroll = function() {
var ypos = window.pageYOffset;
var style = rule(".text-first").style;
style.top = ypos * 0.4 + "px";
if(parseInt(getComputedStyle(txtfirst).top) > 300) {
style.top = "0px";
}
}
The rule function above returns the CSS rule (one containing the set CSS properties) with matching selector (e.g. .text-first or html, body) from the first found stylesheet (you only have one). The style property of a a rule refers to an object which contains all CSS properties set in the rule. It behaves the same as the inline style object. Observe that you aren't using inline styles anywhere above, you write to the stylesheet object (as initialized by the <style>...</style> fragment of your document) and read back computed values.
Fixing problems with using scroll event for animation
First of all, did you know that older versions of iOS did not fire the scroll event as you scrolled? That would stop your parallax effect dead in its tracks, as a single scroll event would be fired after the user stops scrolling. This has to do with the way browsers do page scrolling -- to achieve smooth page scrolling animation using constrained mobile CPU resource, running JavaScript code courtesy of a scroll event handler 60 times per second was just deemed too generous an offer, and Apple instead went for the controversial solution, occupied with good UX as they are.
Anyway, what to do then if not use scroll event? You could use the good old setInterval:
function rule(selector) {
var sheet = document.styleSheets[0];
return Array.prototype.find.call(sheet.cssRules, rule => rule.selectorText == selector);
}
var txtfirst = document.getElementById("txtfirst");
var old_window_pageYOffset = window.pageYOffset;
setTimeout(function() {
var ypos = window.pageYOffset;
if(ypos != old_window_pageYOffset) return;
old_window_pageYOffset = ypos;
var style = rule(".text-first").style;
style.top = ypos * 0.4 + "px";
if(parseInt(getComputedStyle(txtfirst).top) > 300) {
style.top = "0px";
}
}, 1000 / 60);
What the above does is makes sure a function is called 60 times per second for the entire lifetime of your page, but checks on every invocation if the scroll position of the window has changed since last invocation, invoking the old code only if it has, and doing nothing otherwise. This obviously does not use the scroll event at all. All this said, newer iOS releases have since reverted the behavior and the scroll event is fired with every change of the scrolling position. Meaning you may simply want to use that as baseline and depend on the event instead of setInterval. A free benefit of the latter is that you control the rate at which your parallax effect runs.
I can also suggest using requestAnimationFrame, which is more "intelligent" than setInterval in that the user agent will not invoke your code if it deems animation unnecessary, for example if the entire page tab isn't visible or interactive with the user at the moment. Rest assured your animation will run "when needed":
function rule(selector) {
var sheet = document.styleSheets[0];
return Array.prototype.find.call(sheet.cssRules, rule => rule.selectorText == selector);
}
var txtfirst = document.getElementById("txtfirst");
var old_window_pageYOffset = window.pageYOffset;
requestAnimationFrame(function() {
var ypos = window.pageYOffset;
if(ypos != old_window_pageYOffset) return;
old_window_pageYOffset = ypos;
var style = rule(".text-first").style;
style.top = ypos * 0.4 + "px";
if(parseInt(getComputedStyle(txtfirst).top) > 300) {
style.top = "0px";
}
});
The code above is an okay attempt at the parallax effect, save for minor nitpicks which have little to do with parallax effect alone:
I don't use the on*name* family of functions when we have addEventListener. The former is one property for each handler, and there are no guarantees your script is the sole consumer of these properties -- they may already be set by a browser extension. We can argue whether the Web page author has exclusive ownership and access to all properties they can get their hands on, but at least I have explained my rationale. There is no significant drawback known to me for using addEventListener("scroll", function() { ... }) instead.
You don't need to use both a class name and an ID for an element to refer to it. document.querySelector(".text-field") will return the first available element that has "text-field" among its list of class names.
I've saved the best for last -- Pure CSS Parallax Websites goes through achieving (although not without some small hacks for browsers with bugs) desired effect without any JavaScript at all, relying on the CSS perspective property and some others. It also mentions some of the same things I've warned about above, things that I have attempted to circumvent and explain.
If you don't want to read (and understand) documentation, I suggest you resort to using a convenient abstraction -- a plugin, a framework, a library or something to that end that will save you from having to grok the intricacies of this. Modern CSS and compliant browser model are complex enough for these solutions to exist and thrive.

Related

How do I get the height of a textarea

I need to get the height of a textarea. Seemingly so simple but it's driving me mad.
I have been researching for ages on stackoverflow with no luck: textarea-value-height and jquery-js-get-the-scrollbar-height-of-an-textarea and javascript-how-to-get-the-height-of-text-inside-of-a-textarea, among many others.
This is how it looks currently:
This is how I want it to look, open a full height:
.
Here is my html:
<textarea id="history" class="input-xxlarge" placeholder="Enter the content ..." rows="13"></textarea>
CSS:
.input-xxlarge {
display: inline-block;
height: auto;
font-size: 12px;
width: 530px;
resize: none;
overflow: auto;
}
jQuery:
var textarea = $('#history');
I've tried (inter alia):
1. textarea.height() --> always returns 0
2. textarea.ready(function() { // wait for DOM to load
textarea.height();
}
3. getting scrollheight from textarea as an HTMLTextareaElement (i.e. DOM Element) --> returns 0
4. var contentSpan = textarea.wrapInner('<span>');
var height = contentSpan.height(); --> always returns 0
Please help, I'm at my wit's end!
Ok, I've found a solution. Whether it's the best solution, I don't know, but it works and that, frankly, is all I care about, having spent almost a day on this issue.
Here it is for anyone who faces the same problem:
Select the textarea:
var textarea = $('#history');
Get the textarea's text:
var text = textarea.text();
Create a temporary div:
var div = $('<div id="temp"></div>');
Set the temp div's width to be the same as the textarea. Very important else the text will be all on one line in the new temp div!:
div.css({
"width":"530px"
});
Insert the text into the new temp div:
div.text(text);
Append it to the DOM:
$('body').append(div);
Get the height of the div:
var divHeight = $('#temp').height();
Remove the temp div from the DOM:
div.remove();
Had a similar issue, in my case I wanted to have an expand button, that would toggle between two states (expanded/collapsed). After searching also for hours I finally came up with this solution:
Use the .prop to get the content height - works with dynamically filled textareas and then on a load command set it to your textarea.
Get the inner height:
var innerHeight = $('#MyTextarea').prop('scrollHeight');
Set it to your element
$('#MyTextarea').height(innerHeight);
Complete code with my expand button(I had min-height set on my textarea):
$(document).on("click", '.expand-textarea', function () {
$(this).toggleClass('Expanded');
if($(this).hasClass('Expanded'))
$($(this).data('target')).height(1);
else
$($(this).data('target')).height($($(this).data('target')).prop('scrollHeight'));
});
Modern answer: textarea sizing is a few lines of ES6 implementable two primary ways. It does not require (or benefit from) jQuery, nor does it require duplication of the content being sized.
As this is most often required to implement the functionality of auto-sizing, the code given below implements this feature. If your modal dialog containing the text area is not artificially constrained, but can adapt to the inner content size, this can be a perfect solution. E.g. don't specify the modal body's height and remove overflow-y directives. (Then no JS will be required to adjust the modal height at all.)
See the final section for additional details if you really, truly only actually need to fetch the height, not adapt the height of the textarea itself.
Line–Based
Pro: almost trivial. Pro: exploits existing user-agent behavior which does the heavy lifting (font metric calculations) for you. Con: impossible to animate. Con: extended to support constraints as per my codepen used to explore this problem, constraints are encoded into the HTML, not part of the CSS, as data attributes.
/* Lines must not wrap using this technique. */
textarea { overflow-x: auto; white-space: nowrap; resize: none }
for ( let elem of document.getElementsByTagName('textarea') ) {
// Prevent "jagged flashes" as lines are added.
elem.addEventListener('keydown', e => if ( e.which === 13 ) e.target.rows = e.target.rows + 1)
// React to the finalization of keyboard entry.
elem.addEventListener('keyup', e => e.target.rows = (elem.value.match(/\n/g) || "").length + 1)
}
Scrollable Region–Based
Pro: still almost trivial. Pro: animatable in CSS (i.e. using transition), though with some mild difficulty relating to collapsing back down. Pro: constraints defined in CSS through min-height and max-height. Con: unless carefully calculated, constraints may crop lines.
for ( let elem of document.getElementsByTagName('textarea') )
elem.addEventListener('keyup', e => {
e.target.style.height = 0 // SEE NOTE
e.target.style.height = e.target.scrollHeight + 'px'
})
A shocking percentage of the search results utilizing scrollHeight never consider the case of reducing size; for details, see below. Or they utilize events "in the wrong order" resulting in an apparent delay between entry and update, e.g. pressing enter… then any other key in order to update. Example.
Solution to Initial Question
The initial question specifically related to fetching the height of a textarea. The second approach to auto-sizing, there, demonstrates the solution to that specific question in relation to the actual content. scrollHeight contains the height of the element regardless of constraint, e.g. its inner content size.
Note: scrollHeight is technically the Math.max() of the element's outer height or the inner height, whichever is larger. Thus the initial assignment of zero height. Without this, the textarea would expand, but never collapse. Initial assignment of zero ensures you retrieve the actual inner content height. For sampling without alteration, remove the height override (assign '') or preserve (prior to) then restore after retrieval of scrolllHeight.
To calculate just the height of the element as-is, utilize getComputedStyle and parse the result:
parseInt(getComputedStyle(elem).height, 10)
But really, please consider just adjusting the CSS to permit the modal to expand naturally rather than involving JavaScript at all.
Place this BEFORE any HTML elements.
<script src="/path/to/jquery.js"></script>
<script>
$(document).ready(function(){
var textarea = $('#history');
alert(textarea.height()); //returns correct height
});
</script>
You obviously do not have to alert it. I was just using an easily visible example.
Given a textarea with an id of "history", this jQuery will return it's height:
$('#history').height()
Please see a working example at http://jsfiddle.net/jhfrench/JcGGR/
You can also retrieve the height in pixels by using $('#history').css('height'); if you're not planning on doing any calculations.
for current height in px:
height = window.getComputedStyle(document.querySelector('textarea')).getPropertyValue('height')
for current width in px:
width = window.getComputedStyle(document.querySelector('textarea')).getPropertyValue('width')
change 'textarea' to '#history' or like a css selector. or textarea, since a variable is declared to select element.

Javascript: Check collision between two divs

Is there any way to check, if DIV with name for example "character" is overlaping DIV with name "ground" ?
I want to do this with clean Javascript, I know that jQuery is better, but that's what I don't want.
I saw this post: check collision between certain divs? , but it does not return anything.
Thanks for help.
First, I'd suggest you check out the HTML5 canvas element, as by the sounds of it, you want to make a game, and the canvas is great for that ;)
But, to answer your question, you could create or get div elements with document.createElement() or getElementById() respectively, and get their style properties either by getting their JS-set values (element.style) or use getComputedStyle if you'd prefer to set initial values in CSS.
Make sure that, however you get these CSS properties, they'll need to be parsed into something that JS can digest. For integer-based positions, parseInt() usually does the trick.
Next, you do the math. In this case, you'd want to see if the character div's top, plus its height, is greater than the top position of the ground. If it is, it has collided.
To set the style back to the div, you can just set the style property.
Here's an example (copied from this fiddle):
var character = document.getElementById("character");
var ground = document.getElementById("ground");
//We could use getComputedStyle to get the style props,
//but I'm lazy
character.style.top = "10px";
character.style.height = "40px";
ground.style.top = "250px";
//For every 33ms (about 30fps)
setInterval(function(){
//Get the height and position of the player
var charTop = parseInt(character.style.top),
charHeight = parseInt(character.style.height);
//and the top of the ground
var groundTop = parseInt(ground.style.top);
//linear gravity? Why now?
charTop += 5;
//If the character's bottom is hitting the ground,
//Stop moving
if(charTop + charHeight > groundTop) {
charTop = groundTop - charHeight;
}
//Set the character's final position
character.style.top = charTop + "px";
},33);
#character {
position: absolute;
width: 40px;
height: 40px;
left: 50px;
background-color: #F00;
}
#ground {
position: absolute;
width: 300px;
height: 60px;
left: 0px;
background-color: #A66;
}
<div id="character"></div>
<div id="ground"></div>
One more thing: While there are convoluted ways to get element positions when elements use different positioning properties (ex: the player uses top/left coordinates, where the ground uses bottom), it's a lot harder to manage.
The only jQuery that was being used in that linked answer was to get with width,height, and position of the divs, which are somewhat trivial to retrieve using pure JS:
CSS / JavaScript - How do you get the rendered height of an element?
jquery position() in plain javascript
How do I retrieve an HTML element's actual width and height?
It's not returning anything because the .top .left and height variables in the return statement were relying on jQuery functions that retrieve the information mentioned above.

change css on scroll event w/ requestAnimation Frame

I want to change the background color of in-viewport elements (using overflow: scroll)
So here was my first attempt:
http://jsfiddle.net/2YeZG/
As you see, there is a brief flicker of the previous color before the new color is painted. Others have had similar problems.
Following the HTML5 rocks instructions, I tried to introduce requestAnimationFrame to fix this problem to no avail:
http://jsfiddle.net/RETbF/
What am I doing wrong here?
Here is a simpler example showing the same problem: http://jsfiddle.net/HJ9ng/
Filed bug with Chromium here: http://code.google.com/p/chromium/issues/detail?id=151880
if it is only the background color, well why don't you just change the parent background color to red and once it scroll just change it to pink?
I change your CSS to that
#dad
{
overflow-y: scroll;
overflow-x: hidden;
width: 100px;
height: 600px;
background-color:red;
}​
I remove some of you Jquery and change it to this
dad.bind('scroll', function() {
dad.css('background-color', 'pink');
});
And I remove this line
iChild.css('backgroundColor', 'red');
But is the Red color it is important that won't work for sure http://jsfiddle.net/2YeZG/5/
I like Manuel's Solution.
But even though I don't get what you're exactly trying to do, I want to point out a few things.
In your fiddle code, I saw that you included Paul Irish's Shim for requestAnimationFrame.
But you never use it.
(It's basically a reliable setTimeOut, nothing else) it's from frame based animations.)
So since you just want to change some CSS properties, I don't see why you would need it. Even if you want transitions, you should rely on CSS transitions.
Other than that your code could look something like
dad.bind('scroll', function() {
dad.css('background-color', 'pink');
eachElemNameHere.css('background-color','randomColor');
});
Also you should ideally not use something like that if you can help it. You should just add and remove class names and add all these properties in your CSS. Makes it work faster.
Also, again I don't quite get it, but you could use the jQuery function to find out each elements' position from the top to have better control.
Your problem seems to be that you only change the background color of the elements which have already been scrolled into view. Your code expects that the browser waits for your code to handle the scroll event before the browser redraws its view. This is most probably not a guarantee given by the HTML spec. That's why it flickers.
What you should do instead is to change the elements which are going to be scrolled into view. This is related to off screen rendering or double buffering as it is called in computer games programming. You build your scene off screen and copy the finished scene to the visible frame buffer.
I modified your first JSFiddle to include a multiplier for the height of the scroll area: http://jsfiddle.net/2YeZG/13/.
dad.bind('scroll', function() {
// new: query multiplier from input field (for demonstration only) and print message
var multiplier = +($("#multiplier")[0].value);
$("#message")[0].innerHTML=(multiplier*100)-100 + "% of screen rendering";
// your original code
var newScrollY = newScrollY = dad.scrollTop();
var isForward = newScrollY > oldScrollY;
var minVal = bSearch(bots, newScrollY, true);
// new: expand covered height by the given multiplier
// multiplier = 1 is similar to your code
// multiplier = 2 would be complete off screen rendering
var newScrollYHt = newScrollY + multiplier * dadHeight;
// your original code (continued)
var maxVal;
for (maxVal = minVal; maxVal < botsLen; maxVal++) {
var nxtTopSide = tops[maxVal];
if (nxtTopSide >= newScrollYHt) {
break;
}
}
maxVal = Math.min(maxVal, botsLen);
$(dadKids.slice(minVal, maxVal)).css('background', 'pink');
});
Your code had a multiplier of 1, meaning that you update the elements which are currently visible (100% of scroll area height). If you set the multiplier to 2, you get complete off screen updates for all your elements. The browser updates enough elements to the new background color so that even a 100% scroll would show updated elements. Since the browser seldom scrolls 100% of the area in one step (depends of the operating system and the scroll method!), it may be sufficient to reduce the multiplier to e.g. 1.5 (meaning 50% off screen rendering). On my machine (Google Chrome, Mac OS X with touch pad) I cannot produce any flicker if the multiplier is 1.7 or above.
BTW: If you do something more complicated than just changing the background color, you should not do it again and again. Instead you should check whether the element has already been updated and perform the change only afterwards.

jquery javascript resize jitter

I have some simple javascript that I'm using to auto-adjust the width of elements on pages and to vertically center the text on these pages.
My script works, but in IE9 and a little in Safari there is a distinct moment where the elements are not resized and they jump across the page. It's just a momentary flash, but it bugs me as I'm generally not a "good enough" kind of person. Here is my own script:
$(document).ready(function() {
var containerwidth = $("#main_content").css("width");
var picwidth = $(".picture").css("width");
$(".picture").parent().css("width", picwidth);
var correctwidth = parseInt(containerwidth) - parseInt(picwidth);
$(".main-text").css("width",correctwidth-25);
if( $(".margins").css("width") ) {
$(".title").css("width", parseInt($(".width-set").css("width"))+10);
} else {
$(".title").css("width", parseInt($(".title").parent().css("width"))-10);
}
var container_height = $(".main-text").height();
var text_height = $(".vert-align").height();
var offset = (container_height - text_height) / 2;
$(".vert-align").css("margin-top", offset);
[...]
});
I realize the use of explicit offsets and whatnot is hackish, but I'm in a hurry and will correct it later. And yes, I am using jQuery.
This is stored in a file, and I've tried both calling it in the head, and also directly after the elements it affects, but the result is the same. Is this jitter just a fact of life for using element manipulation with javascript, or is there some solution I've missed on the forums?
Thanks!
I suspect the reason is because you are calling this in the $(document).ready(), which runs after the DOM is loaded (i.e. your elements are already displayed).
If you absolutely have to resize elements after they've loaded, the only thing I can think of that might help is having an overlay that covers the entire window, maybe something like:
#overlay{
position: fixed;
width: 100%; height: 100%;
background: #fff;
z-index: 9001;
}
And then hiding the overlay via $("#overlay").hide() after the resizing in your $(document).ready() function. I haven't tested this so I don't know if it works. You might have to add a short setTimeOut as well.
To be honest, though, this solution feels very dirty. Hopefully someone else can think of something more elegant.
#ZDYN is correct. The "flicker" happens when the page is displayed but the jQuery code has not been executed.
You can try to set in the css your elements to "visibility: hidden" so they will have their dimensions for the calculations, then change the visibility to "visible" after the resizing.

jQuery and margin: 0 auto

So, this is a problem that's been asked before, but I'm hoping we can lay it to rest: I'm using jQuery 1.4. If I define the style
#obj { margin: 0 auto; }
and then do
$('#obj').css('marginLeft');
the result is the computed value in pixels. Is there any way to tell whether those pixels come from the auto calculation or not, without parsing document.styleSheets?
This solution would also be triggered if the margins were set to percentages, but it might be good enough for your purposes. Basically, you record which margins change on resize. So you'd record the margins before resize to an array:
var aMargins = [];
$('.yourObjs').each(function(i,obj){
var objML = $(obj).css('marginLeft');
aMargins.push(objML);
});
Then resize the window, see which margins changed (these will be either 'auto' or %), do what you need to do to them and return he window to original size:
var wW = $(window).width();
var wH = $(window).height();
window.resizeTo(wW - 5, wH);
$('.yourObjs').each(function(i,obj){
if ($(obj).css('marginLeft') != aMargins[i]) {
// your centering code here
}
}
window.resizeTo(wW,wH);
If your centering code just adjusts the left margin then this should work fine for % based margins too. I can't test this code or provide an example because I'm on the road and writing from my phone, but hopefully this works or helps you come up with something that will.
You can't get the auto from the element itself, because styles are cascading, what if you had this?
#obj { margin: 0 auto; }
div #obj { margin: 0 10px; }
Which is it? Depends on the page and how it cascades, the basic concept is you're getting the calculated style properties on that element, what's in the stylesheet doesn't matter, there could be 20 stylesheets, etc.
Basically it boils down to this: getting auto vs 000px is a really rare request and would required a lot of extra code to figure out, so much so that it's an easy case of "no, this doesn't belong in core". However, there are plugins to do CSS parsing.
Short answer: jQuery core cannot (doesn't have code to) do this, jQuery with plugins, or just JavaScript in general yes you can.

Categories