I am using PhantomJS to get positions of certain elements on the page, for instance iframes or objects. Presently I
page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js", function() {
page.evaluate(function() {
// Select attr etc..
position = $(this).position();
offset = $(this).offset();
});
});
I've tried adding $(window).load() to the formula but it still isn't returning the correct element position a lot of the time. Especially with iFrames and Objects. Perhaps they're positioned after the DOM has loaded? Either way does anyone have any ideas how I can improve or change method to get much more accurate positions?
Kind regards,
Fab
What position is being returned? How is it incorrect?
A few things.
1) When page.evaluate is invoked the page has already loaded. That happens at page.open time. window.onload will never fire inside page.evaluate; it has already fired.
2) I don't know what this is within page.evaluate.
3) Within a PhantomJS context, there's no real reason to use jQuery. You're better off using standard constructs such as document.documentElement.scrollTop.
Try this:
page.evaluate(function() {
console.log(document.documentElement.scrollTop);
console.log(document.getElementById("my-frame").getBoundingClientRect());
});
Related
I'm trying to create a generic function that can be placed just once in my site and work across multiple pages, nice and lightweight.
I want to be able to make certain divs on the site fade-in when you reach 10px above them on the scroll.
I want to do this by simply adding the following attributes to my divs:
.fade-in-block
#specific-block-name
The idea is that I could go through the site, add this class and an ID, and the animation would work.
I almost have it working except for one thing, the scroll listening constantly continues to console.log after the function has been called. I don't like this as it feels like it's going to be constantly trying to apply the animation, which won't really be seen from the front-end but I feel the constant maths behind the scenes could slow stuff down.
Here is my jQuery:
$('body .fade-in-block').each(function(){
var block = '#'+$(this).attr('id');
console.log('Block class is = '+block);
var offset = $(block).offset().top;
var $w = $(window).scroll(function () {
if ($w.scrollTop() > offset - 10) {
console.log('reached block turn-on point for '+block);
$(block).removeAttr('id'); // remove the ID from the element so the script doesn't continue to find the element
// fade and rise animation here
}
});
});
And here is a JSFiddle. It works just fine, but once you hit the block you'll see it logs constantly every pixel scrolled.
I tried to remedy this by removing the selecting id from the element once the event has occurred, but it continues to run.
Scroll and resize events both have this problem and the solution is said to be debouncing. However, I've never actually gotten debouncing to work properly. Instead I typically create a sort of switch that is turned off once the scroll condition has activated. In your case, since you have multiple elements, you would need to assign a switch to each element.
$(window).on('scroll', function(){
$('.fade-in-block').each(function(){
var appear = $(this).attr('data-appeared');
if(!appear){
$(this).attr('data-appeared', true);
//do something to $(this)
}
})
})
Here I'm adding a data attribute after it has appeared and checking for it again once it has.
I'm sure this sounds a little odd, but here's the background...
We utilize a company that loads their chat program, so we can support our customers, into our page. This is done via javascript and jquery, and creates a structure like this:
<div id="myid" style="...; right: 0px;..."><div><iframe></iframe></div></div>
There's a WHOLE lot more to that, but those are the important parts. Now the tool allows us to put custom scripting, which will be placed in the iframe. My goal is to just remove the "right: 0px", which I have done via the below code, but I don't want to put that code on every page that this tool integrates with. I would like to load it into the tool, and have it run when the iframe and divs are created.
working code on parent:
$(document).ready(function() {
function checkPos() {
$('#myId').each(function() {
var oldstyle = $('#myId').attr('style');
var newstyle = oldstyle.replace(' right: 0px;','');
$('#myId').attr('style', newstyle);
});
setTimeout(checkPos, 100);
};
$(document).ready(function() {
setTimeout(checkPos, 100);
});
});
Once placed in the code include method they provide, I have trouble having it wait until the div tag actually has the "right: 0px;" in its style tag. the only thing I need to run is the three lines in the $('#myId').each(function()
Basically, I need help with having the script in the iframe target the div that the iframe is nested in.
Assuming that whatever tool your using actually lets you pass in a custom script to the content rendered in the iframe (seems fishy to me), a better way of modifying the style in jquery is to use the css function:
$('#myId').css('right', '0px');
Notice I removed the $.each function as well. You are targeting an element with an id, so there isn't any need to iterate.
Edit:
Anyways, back to the problem of delaying execution to when the target, #myId, actually exists. If they are really injecting your javascript into their page (again, seems fishy), then attaching the above code to the $(document).ready() event should do the trick, as long as this listener is attached to their document.
If all else fails, try to use the waitUntilExists plugin, here:
Source:
https://gist.github.com/buu700/4200601
Relevant question:
How to wait until an element exists?
I am trying to do detect element resize with jquery resize plugin (http://benalman.com/projects/jquery-resize-plugin/) on jquery 1.10.2.
$("#element").resize(function(){
console.log("resize");
});
I did the testing on Firefox 25 and I get this error:
Error: TypeError: r is undefined
Source File: jquery.ba-resize.min.js
Line: 9
How can I solve it? Is there any alternative way / plugin for doing this?
Thank you.
You need a resize sensor which is bundled with css-element-queries of https://github.com/marcj/css-element-queries.
new ResizeSensor($('.elements'), function(){
console.log('resize')
});
jQuery's .resize only works on window object since only window has a event onresize. All other element haven't and thus you need a polyfill for that. I've seen a lot of other jQuery plugins that allow you to listen on resize changes of all element types, but take care: These are incredible slow as they use setTimeout() or interval to check it's dimension change instead of setting up a real resize sensor like the one you can find in the link above.
Using Clay.js (https://github.com/zzarcon/clay) it's quite simple to detect changes on element size:
var el = new Clay('.element');
el.on('resize', function(size) {
console.log(size.height, size.width);
});
DIV does not fire a resize event, so you won't be able to do exactly what you've coded, but you could look into monitoring DOM properties.
If you are actually working with something like resizables, and that is the only way for a div to change in size, then your resize plugin will probably be implementing a callback of its own.
Remove that plug-in, jQuery done with it self.
var element = $('#element');
var current_size = element.width()+'x'+element.height();
element.resize(function(){
var this_size = $(this).width()+'x'+$(this).height();
if(current_size!==this_size){
console.log('changed');
current_size = this_size;
}
});
PS : Doesn't test
For example, if I have this:
$('#button').click(function() {
$.get('/question', function(data) {
$('#question').html(data);
$('#question').dialog( ... );
});
return false;
});
Will the user see the question content for a brief moment before the dialog is shown?
Note: Normally I'd just hide the #question manually, but there's actually a step in between html() and dialog() with another jQuery plugin where the content must not be 'hidden'.
Short Answer
Yes, it's possible that the user will see the question content for a brief moment before the dialog is shown.
The Fix
To guarantee you won't momentarily see the contents of #question before displaying the dialog, absolutely position #question offscreen before displaying it. After that, call the jQuery plugin that requires #question to be displayed. Finally, hide #question and restore its position.
CSS
#question
{
display: none;
}
JavaScript
$('#button').click(function() {
$.get('/question', function(data) {
var question = $('#question');
question.html(data);
var position = question.css('position');
var top = question.css('top');
var left = question.css('left');
question.css({ position: 'absolute', top: -1000, left: -1000 }).show();
//whatever you need to do with #question while it's not hidden
question.hide().css({ position: position, top: top, left: left });
question.dialog( ... );
});
return false;
});
The browser will render the DOM up until that call, at which point it will stop and parse/execute your js. This is why it's considered best practice to put all script tags at the bottom of a page (so that the browser can render enough of the DOM so your visitors aren't stuck staring at a blank white screen).
Using
$(document).ready();
can alleviate this to an extent, but if you're truly concerned with when it is added to the DOM, make sure your code is added at the very bottom of your HTML's body tag.
References:
http://developer.yahoo.com/blogs/ydn/posts/2007/07/high_performanc_5/
In your case absolute not, because you are using a framework. It works like this:
1) Script code is requested from external files as the page progressively loads. An HTML parser has to parse the script tags before there is any awareness of a script request. This code executes when called, but it is fed into the JavaScript interpreter the moment it is available to the JavaScript interpreter.
2) Script code resident directly in the page is fed into the interpreter as the HTML code is parsed by an HTML parser and a script tag is encountered. Code inside functions executes when called, with one exception. Otherwise code executes immediately upon interpretation. The one exception is when a function block is immediately followed by "()" which indicates immediate invocation.
3) Most code that executes initially executes from function calls made with the "onload" event. The onload event occurs when the static DOM is fully available from the HTML parser and when all asset requests from the initial static HTML are requested. In some edge cases with older browsers it is possible for conflicting conditions to occur that create a race condition in the page that prevents the onload event from ever firing or after an extraordinary delay.
4) You are using jQuery, so you are at a severe disadvantage with regards to speed of availability. jQuery code is JavaScript code, so it has to enter the JavaScript interpreter exactly like any other JavaScript. All the prior points from this post must be observed before any jQuery code can execute.
5) When I perform A/B testing I need code to execute as absolutely as early as possible and as fast as possible to minimize flicker on the page, so frameworks are definitely not an option. In this case I follow these steps:
5a) I find the first id attribute directly after the DOM node(s) that I need to access.
5b) I write a function to test for the availability of this node. If the node is available then the areas above it are available, so I am know I am solid. Consider the following example:
var test = function () {
var a = document.getElementById("first_node_lower_than_I_need");
if (a !== null && typeof a === "object") {
//my code here, because I know my target area is available
} else {
setTimeout(test, 100);
}
};
setTimeout(test, 100);
5c) Notice in the sample code above that I call my function with a setTimout delay to give the DOM a fighting chance. If the function executes early that is okay because I am calling it recursively with a delay to give the DOM some extra time to load. If you set your delay to 50ms or lower you are increasing execution time in IE8 and lower because of numerous unnecessary calls for the function. I recommend keeping the delay at 100ms for an ideal balance cross browser, but if you really want rapid execution in new browsers then set the first delay to 50ms, this is the one outside the function, and keep the other at 100ms.
5d) Minimize your use of innerHTML property with the above method, or be very familiar with the targeted page to know when it is okay to use innerHTML. The problem with innerHTML is that it changes the page output without reporting those changes back to the parsed DOM in memory, which normally is an irrelevant disconnect. However, in this case it is certainly relevant because of how fast and early your injected code can execute. This is a problem because other code that executes later, such as with the onload event or jQuery's ready event, will either overwrite your changes or will not be able to find their respected DOM load and simply drop their execution all together. This is particularly an important concern if you are targeted a very high level node in the DOM tree, so for your safety be very specific when selecting nodes to use innerHTML or just use DOM methods. This is a bit more complicated in that you cannot use a DOM method only solution because you cannot change text nodes with the nodeValue method cross-browser as this is not supported in IE7.
If you need to execute JavaScript code before completion of DOM parsing then do not use a JavaScript framework. Write using plain regular JavaScript code and optimize the hell out of it. Otherwise you will always have flicker and the larger of the static HTML download the longer and more noticeable that flicker will be. Additionally, jQuery code tends be far slower to execute than regular optimized JavaScript due to its reliance on CSS like selectors. If your injected jQuery code is required to perform a large task on a very large static HTML document then it is unlikely to complete execution in IE7 without timing out.
This is the reason initializing any DOM-related activity should be done/triggered from within $(document).ready() .
So if you put you $.get statement inside of doc ready, you can ensure that all the elements in the HTML have been rendered and are ready to be interacted with via JS.
$(document).ready(function () {
$.get('/question', function(data) {
$('#question').html(data);
$('#question').dialog( ... );
});
});
I have a long jQuery mobile page and would like to scroll to an element halfway down this page after the page loads.
So far I've tried a few things, the most successful being:
jQuery(document).bind("mobileinit", function() {
var target;
// if there's an element with id 'current_user'
if ($("#current_user").length > 0) {
// find this element's offset position
target = $("#current_user").get(0).offsetTop;
// scroll the page to that position
return $.mobile.silentScroll(target);
}
});
This works but then the page position is reset when the DOM is fully loaded. Can anyone suggest a better approach?
Thanks
A bit late, but I think I have a reliable solution with no need for setTimeout(). After a quick look into the code, it seems that JQM 1.2.0 issues a silentScroll(0) on window.load for chromeless viewport on iOS. See jquery.mobile-1.2.0.js, line 9145:
// window load event
// hide iOS browser chrome on load
$window.load( $.mobile.silentScroll );
What happens is that this conflicts with applicative calls to silentScroll(). Called too early, the framework scrolls back to top. Called too late, the UI flashes.
The solution is to bind a one-shot handler to the 'silentscroll' event that calls window.scrollTo() directly (silentScroll() is little more than an asynchronous window.scrollTo() anyway). That way, we capture the first JQM-issued silentScroll(0) and scroll to our position immediately.
For example, here is the code I use for deep linking to named elements (be sure to disable ajax load on inbound links with data-ajax="false"). Known anchor names are #unread and #p<ID>. The header is fixed and uses the #header ID.
$(document).bind('pageshow',function(e) {
var $anchor;
console.log("location.hash="+location.hash);
if (location.hash == "#unread" || location.hash.substr(0,2) == "#p") {
// Use anchor name as ID for the element to scroll to.
$anchor = $(location.hash);
}
if ($anchor) {
// Get y pos of anchor element.
var pos = $anchor.offset().top;
// Our header is fixed so offset pos by height.
pos -= $('#header').outerHeight();
// Don't use silentScroll() as it interferes with the automatic
// silentScroll(0) call done by JQM on page load. Instead, register
// a one-shot 'silentscroll' handler that performs a plain
// window.scrollTo() afterward.
$(document).bind('silentscroll',function(e,data) {
$(this).unbind(e);
window.scrollTo(0, pos);
});
}
});
No more UI flashes, and it seems to work reliably.
The event you're looking for is "pageshow".
I was digging a lot this issue, also at jQuery mobile official forum.
Currently it seems that there is no solution (at least for me).
I tried different events (mobileinit, pageshow) and different functions (silentscroll, scrolltop) as suggested above, but, as a result, I always have page scrolled until all images and html is finished loading, when page is scrolled to top again!
Partial and not really efficient solution is using a timer as suggested in comment to sgliser's answer; unfortunately with a timeout is difficult to know when page will be fully loaded and if scroll happened before that, it will scroll back to top at the end of load, while if it happens too long after page has fully loaded, the user is already scrolling page manually, and further automated scroll will create confusion.
Additionally, would be useful to have silentscroll or other function to address a specific id or class and not plain pixels, because with different browsers, resolutions and devices it may give different and not correct positioning of the scroll.
Hope someone will find a smarter and more efficient solution than this.