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.
Related
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.
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.
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.
I have an HTML page layout - something like this:
<div id='header'>
Header content
</div>
<div id='main_content'>
some content
</div>
The content in the #main_content div may be really long, or, next to nothing (i use it as part of a template across an entire site).
What I want is for it to minimally be the height of the viewport (minus the height of the header div above that - but no need to get that value, lets just use 100px for sake of discussion).
I can't seem to do this via pure CSS. However, I was thinking perhaps I can fire off a Javascript function every 1 second (and presumably fire it off on the resize event as well), check the height of #main_content, and if its not at least (viewport_hight - 100), then set it to be (viewport_height -100).
Problem is, I am not sure if this is do-able, and, not sure if there is a 'better way'.
Has anyone faced this issue and have a great solution?
Thanks.
Unfortunately, this is not possible with pure CSS. You will need to use JavaScript.
I'm unsure why you would need to call a JavaScript function every second. If it's called when a resize event is triggered and when the page loads, this should be sufficient.
Can you use the CSS min-height property? You can still calculate the value with javascript if necessary.
Take a look at this, I think it will help you:
http://www.xs4all.nl/~peterned/examples/csslayout1.html
this can be solved with CSS AND height=100% .. the following code demonstrates. the header here is 25px tall (works on all current browsers):
<div id='viewport' style='height:50%; padding-top: 25px; border: 4px solid green'>
<div id='header' style='margin-top: -25px'>
Header content
</div>
<div id="main_content" style='height: 100%; border: 1px solid red'>
some content
</div>
</div>
Thanks for the feedback. Here is what I wound up doing that worked:
Firs I used pure CSS for Firefox. For IE, used this jquery script (slightly modified for generic use)
$(document).ready(
function () {
// (I have some other not relevant code here as well in here)
if ( $j.browser.msie ){ setContentHeight(); }
}
);
function setContentHeight() {
// get viewport height
var viewportHeight = $(window).height();
// if body div is not tall enough, make it minimum height
if ( ( $('#body_content_div').height() - 275 ) < viewportHeight ) {
var desiredHeight = viewportHeight - 275;
$('#body_content_div').css("height", desiredHeight + "px");
}
// set this function to fire off 1x per second
// because ajax calls may re-populate the div at any time
setTimeout("setContentHeight()", 1000);
}
on document load it sets desired height
function gets called 1x per second to account for resizing and ajax (could have called on resize() event, but that would not account for ajax.
When writing a Javascript a function that I had gotten help from earlier, which gets the height of an element that is hidden, someone reffered me to the Prototype function getDimensions(). In the example, they set "visibility: hidden; position: absolute; display: block;", which effectively lets us measure what the clientHeight would be if it were being displayed. Then they set it all back and you can go about your business. I haven't used prototype, but I would assume that works fine. However, when I tried to mimic the same function in my own code, the use of "position: absolute;" threw off the measurement. It works fine without it, but its use is what allows us to do this for a split second without skewing the design. My version is below, any idea why it isn't working?
var objStyle = obj[objName].style;
// Record original style values
var visibility = objStyle.visibility;
//var position = objStyle.position;
var display = objStyle.display;
// Modify object for measuring
objStyle.visibility = "hidden";
//objStyle.position = "absolute";
objStyle.display = "block";
// Measure height
height = obj[objName].clientHeight;
// Fix object
objStyle.visibility = visibility;
//objStyle.position = position;
objStyle.display = display;
// Return height
return parseInt(height);
Thanks in advance for your help.
I don't know if it works while invisible, but jQuery has some options here - in particular the height function; worth a look? Based on your example, something like:
height = $(obj[objName]).height();
Are you seeing this only on a cetain browser, or on all browsers? Prototype's getDimensions() does a check for Safari (and possibly other buggy browsers), you should try putting that in your code as well and see if it fixes the issue.
It could also be due to the fact that you're using obj[objName] as opposed to document.getElementById() - AFAIK these will return slightly different objects, which could cause the inconsistency you're seeing.
I usually measure my heights with .offsetHeight, something like:
var h = document.getElementById(divname).offsetHeight;
When I need to measure something, if it has position:absolute;
I usually run into this when I have two columns and one is absolute, and the parent needs to be pushed down by the one that's absolute if that's bigger than the other one. I'll use the offsetHeight to set the parent height if it's bigger that the height of the other column.