How can I get an indication of what part of a long document is currently being displayed?
E.g. if my html contains 1,000 lines
1
2
3
...
999
1000
and the user is near the middle showing the 500th line then I would like to get "500\n501\n502" or something like that.
Obviously most scenarios would be more complex than this, but my requirement is to find which text is currently being displayed in the browser viewport so I can show a status value appropriate to the current text.
Thanks
Martin
If you have jQuery, you can use this function to check if a DOM element is currently shown in the viewport:
function isInView(elem) {
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom));
}
You can get a value in pixels from the scrollTop property:
document.body.scrollTop = 40;
To know what part of your document that is visible, you could loop through (say) all p-tags until you find one with a negative scrollTop value. The one before that is the one at the top of the window.
I've just seen a piece of sample code on msdn
function isinView(oObject)
{
var oParent = oObject.offsetParent;
var iOffsetTop = oObject.offsetTop;
var iClientHeight = oParent.clientHeight;
if (iOffsetTop > iClientHeight) {
alert("Special Text not in view. Expand Window to put Text in View.");
}
else{
alert("Special Text in View!");
}
}
Yes, there is a way. I will use YUI's API to illustrate my example. First your text must be in some sort of dom element, whether its a span, div, p or anything, it must be in a element. Here I will assume list item
var viewPortY = YAHOO.util.Dom.getDocumentScrollTop(),
viewPortHeight = YAHOO.util.Dom.getViewportHeight(), i = 0,
// get all the dom elements that contain the text, sorry if this isn't exact, its just a rough example
items = YAHOO.util.Dom.getElementBy(null, 'li', document.getElementById('item-container')),
viewedItems = [];
for (i = 0 ; i < items.length; i++) {
var y = YAHOO.util.Dom.getY(items[i])
if (y > viewPortY && y < (viewPortY + viewPortHeight)) {
viewedItems.push(items[i])
}
}
So essentially, I get all the dom objects that contain the text your interested in. I then loop through, and whoever's Y co-ordinate is between the viewports Y and Y + ViewPort Height, I put in an array.
I implemented what I thought was a more optimal solution for my environment:
I am writing for Android so I can easily interact with a Java class from javascript. My actual solution involved getting offsetTop of all tags I am interested in and passing the offsets to java.
Also registering an onscroll handler that passed window.pageYOffset throught to the same Java class. Then the java class can compare offsetTop of each tag with pageYOffset to see which tag is at the top of the current viewport.
Related
I have two divs side by side set to height auto. I want them to have equal height, so i combined them as members of an array.
I recurse through the array and set the not-tallest ones to the height of the tallest. Problem is everything i have tried to get the COMPUTED height has resulted in the incorrect value.
I have tried the following:
(els[x].currentStyle) ? h=els[x].currentStyle.height : h=window.getComputedStyle(els[x],null).height;
h = el.clientHeight || el.offsetHeight || el.scrollHeight;
Both of these are yielding 640 px'ish while the computed is 751.8 in my particular showing.
Is there possbily a constant I can use to get the correct height. Like maybe the number im getting would be on a standard size screen (like 960 pixels high or such) then multiple that by the window size?
I have had a lot of good use of this little function I came up with
function getH(id)
{
return document.getElementById(id).offsetHeight;
}
// I have a styles.js file where this function feels right at home. Access this function from anywhere.
This will return the height of any given elements given to the function (by it's ID). So now we'r 1/3 of the way.
Then use the Math.max() function to get the height of the largest element.
var maxH = Math.max(getH("ID1"),getH("ID2"));
This is 2/3 of the way, YAY - Now set the elements height to be the same.
var x = document.getElementById("ID1");
var y = document.getElementById("ID2");
x.style.height = maxH +"px";
y.style.height = maxH +"px"; //Remember the +"px" has to be added as a string, thats the reason for the "".
DONE!! - Now put it all together
I would imagine something like this
function setElementHeight()
{
var maxH = Math.max(getH("ID1"),getH("ID2"));
var x = document.getElementById("ID1");
var y = doc...("ID2");
x.style.height = maxH +"px";
y.style.height = maxH +"px";
}
// Don't forget to include the first function in you'r .js file
This WILL set both ID's to the same height and the height WILL be equal to the highest. That is what you want, isn't it?
--EDIT--
I would throw away the array if I were you. Just have the 2 DIV's each with a unique ID and make them equally tall based upon that.
Molle
If you have the DOM document, you can try the below code:
let divElement = document.getElementById('divId');
let height = document.defaultView.getComputedStyle(divElement).height;
'height' will have the exact height of the element with 'divId' once it is computed.
I am trying to increment the position of an element by, say, x pixels. Here is what I've tried so far:
var top = document.getElementById("something").style.top;
top = top + "300px"
I know that this is not going to work, but I was wondering if it was possible to increment a position value like this.
Because style.top is a string with units on the end of it like "300px" you can only do math with it when you convert just the numeric part to an actual number.
Assuming you have a positioned element (so setting the top value will do something) and you already have a top style set directly on the element and not set via CSS (so getting obj.style.top will actually get you something), you can do it by parsing the number out of the style value like this:
var obj = document.getElementById("something");
var topVal = parseInt(obj.style.top, 10);
obj.style.top = (topVal + 300) + "px";
Working example: http://jsfiddle.net/jfriend00/pt46X/
That won't work fine because, for example, if top had a value of 200px, it would become "200px300px". Try this:
var elem = document.getElementById("something");
elem.style.top = parseInt(elem.style.top, 10) + 300 + "px"
Demo WEEEE!!!!
let top = 0;
let left = 0;
let text = document.getElementById("TextToTranslate");
text.setAttribute("style","top:"+top+"px; "+left+":px;");
use this in a while loop and it works fine, i'm just figuring out how to slow it down so i can see the transition
next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
pos = el.style[direction].split('px')[0];
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
I'm using the simple code above to try and move an element. Now when I breakpoint on this, the value of el.style[direction] is: " ". So then when i try to do anything with it, it breaks. Why would this be? Isn't style.left supposed to return an integer?
Why would this be?
Presumably because it hasn't been set to anything.
Isn't style.left supposed to return an integer?
No. It is supposed to return a string containing the value of the CSS left property as set directly on the element (either by setting the JS property itself or by using a style attribute). It does not get a value from the cascade and it should only be an integer if the value is 0 (since all other lengths require units).
See How to get computed style of a HTMLElement if you want to get the computed value for the property rather than what I described in the previous paragraph.
style provides the original style as calculated from the CSS, not the updated and possibly dynamic style. You probably want currentStyle instead.
next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
var lft = document.defaultView.getComputedStyle(el)[direction];
pos = parseFloat(lft);
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
Note: like Elliot said you'll have to get the currentStyle/computedStyle. Here's a way to make it cross-browser, however when applying styles via JS, this is one good case where some sort of framework (eg Prototype [Scriptaculous], jQuery) would be useful.
Just a comment.
In your code:
> pos = el.style[direction].split('px')[0];
> pos = parseInt(pos, 10) + 10;
The split in the first line is superfluous, in the second line parseInt will convert (say) 10px to the number 10 just as effectively (and more efficiently) than what you have.
pos = parseInt(el.style[direction], 10);
So based on this question I asked, what's the most reliable way of getting position of objects that's crossbrowser? Thx
In general, assuming you have an element named elem, it's actually quite easy to get the X and Y coordinates of the top-left corners of an element, assuming you want these in document coordinates. In all browsers this is returned by the elem.offsetLeft and elem.offsetTop properties.
The only trick you have to be aware of is that if elem is absolutely positioned in another element, say a div with a left / top margin of 20px, these properties will return 0, as it only takes into account the current element and not the entire chain of elements. Luckily we can use a "chain-traversal" function to capture all of the margins of elements associated with a given element, tally them up to get the correct document coordinates.
As Sime Vidas mentioned, there is also JQuery's position() and offset() properties, in this case you would want the offset() properties.
You can also use the getBoundingClientRect() method, however this returns the coordinates of an element relative to its offsetParent and thus is not entirely reliable. Look at the following examples:
// getPosition function
function getPosition(elem){
var dims = {offsetLeft:0, offsetTop:0};
do {
dims.offsetLeft += elem.offsetLeft;
dims.offsetTop += elem.offsetTop;
}
while (elem = elem.offsetParent);
return dims;
}
cont1.style.position = "absolute";
cont1.style.marginLeft = "10px";
cont2.style.position = "absolute";
cont2.style.marginLeft = "10px";
box.style.position = "absolute";
box.style.marginLeft = "10px";
console.log(getPosition(box).offsetLeft); // returns "30"
console.log(getPosition(box).offsetTop); // returns "0"
// or in JQuery
console.log($(box).offset().left) // also returns "30"
console.log($(box).offset().top) // also returns "0"
Also I suggest you read this.
If you want to find the position of an element relative to document use jQuery offset() method.
var p = $("p:last");
var offset = p.offset();
p.html( "left: " + offset.left + ", top: " + offset.top );
.offset() reference: http://api.jquery.com/offset/
If you want to find the poistion of an element relative to its parent then use jQuery position() method.
var p = $("p:first");
var position = p.position();
$("p:last").text( "left: " + position.left + ", top: " + position.top );
.position() reference: http://api.jquery.com/position/
And these methods almost gives perfect result in most of the browsers.
I like element.getBoundingClientRect(). It has good cross-browser support.
var coords = element.getBoundingClientRect();
This gives the coordinates relative to the viewport. To get the coordinates relative to the document, add document.documentElement.scrollTop to the top and document.documentElement.scrollLeft to the left.
coords.top += document.documentElement.scrollTop;
coords.left += document.documentElement.scrollLeft;
But since you are already using jQuery, you may as well just use .offset().
Okay so I am developing a WordPress theme. On the single post page I have a comments div which floats down the page using some jquery. I am also running a modal popup form to log in. This is completely fine on the single page when the #commentWrapper (selector for the jquery floating effect) exists. However on pages where there is no #commentWrapper to float, the modal form doesn't work. I pinned down the problem to this line in my general jQuery call (by removing each line and testing).
Call in general.js, very last call:
jQuery('#commentWrapper').stickyfloat({ duration: 300, easing : 'easeInQuad' });
Actual plugin it refers to:
$.fn.stickyfloat = function(options, lockBottom) {
var $obj = this;
var parentPaddingTop = parseInt($obj.parent().css('padding-top'));
var startOffset = $obj.parent().offset().top;
var opts = $.extend({ startOffset: startOffset, offsetY: parentPaddingTop, duration: 200, lockBottom:true }, options);
$obj.css({ position: 'absolute' });
if(opts.lockBottom){
var bottomPos = $obj.parent().height() - $obj.height() + parentPaddingTop; //get the maximum scrollTop value
if( bottomPos < 0 )
bottomPos = 0;
}
$(window).scroll(function () {
$obj.stop(); // stop all calculations on scroll event
var pastStartOffset = $(document).scrollTop() > opts.startOffset; // check if the window was scrolled down more than the start offset declared.
var objFartherThanTopPos = $obj.offset().top > startOffset; // check if the object is at it's top position (starting point)
var objBiggerThanWindow = $obj.outerHeight() < $(window).height(); // if the window size is smaller than the Obj size, then do not animate.
// if window scrolled down more than startOffset OR obj position is greater than
// the top position possible (+ offsetY) AND window size must be bigger than Obj size
if( (pastStartOffset || objFartherThanTopPos) && objBiggerThanWindow ){
var newpos = ($(document).scrollTop() -startOffset + opts.offsetY );
if ( newpos > bottomPos )
newpos = bottomPos;
if ( $(document).scrollTop() < opts.startOffset ) // if window scrolled < starting offset, then reset Obj position (opts.offsetY);
newpos = parentPaddingTop;
$obj.animate({ top: newpos }, opts.duration );
}
});
};
If I add an if command to see if the selector exists, it all works. I would like to know what the problem is for future website however.
Well, your stickyfloat() method assumes many things, like always being called on a jQuery object that contains at least one element, or that element always having a parent. For instance, consider the following code:
var $obj = this;
// ...
var startOffset = $obj.parent().offset().top;
If the jQuery object your method is called on is empty, or if its first element has no parent (the method was called on $("html")), the code will fail because parent().offset() will be null.
If you want your method to be more robust, you should not assume anything about the object it's called on. A good first step is to make the method chainable, which is always beneficial to your users and will get rid of the first problem. The recommended way of doing that is:
$.fn.stickyfloat = function(options, lockBottom) {
return this.each(function() {
var $obj = $(this);
// The rest of your code.
});
};
Since the code now runs sequentially on each element (if any) through an anonymous function, testing for the parent element's existence can be dealt with by returning early:
var $obj = $(this);
var $parent = $obj.parent();
if (!$parent.length) {
return; // No parent, continue with next element, if any.
}
// Parent element is safe to use.
var parentPaddingTop = parseInt($parent.css('padding-top'));
var startOffset = $parent.offset().top;