I have a force directed graph with "Tips" enabled. I don't want to show tips for those nodes which are hidden i.e. for whom "alpha" is zero. In onShow call back function I am trying to use tips.hide() but it is not hiding the tip. Here is my code.
Tips: {
enable: true,
type: 'Native',
onShow: function(tip, node) {
if( node.getData('alpha') == 0 ) this.fd.tips.hide(false);
else tip.innerHTML = node.name;
}
}
When I drilled down into infovis library jit.js I found something which looks like a bug. Below is the hide function which basically sets style.display to 'none'.
hide: function(triggerCallback) {
this.tip.style.display = 'none';
triggerCallback && this.config.onHide();
}
Now look at the code below.
onMouseMove: function(e, win, opt) {
if(this.dom && this.isLabel(e, win)) {
this.setTooltipPosition($.event.getPos(e, win));
}
if(!this.dom) {
var node = opt.getNode();
if(!node) {
this.hide(true);
return;
}
if(this.config.force || !this.node || this.node.id != node.id) {
this.node = node;
this.config.onShow(this.tip, node, opt.getContains());
}
this.setTooltipPosition($.event.getPos(e, win));
}
},
setTooltipPosition: function(pos) {
var tip = this.tip,
style = tip.style,
cont = this.config;
style.display = ''; //This looks like a problem
//get window dimensions
var win = {
'height': document.body.clientHeight,
'width': document.body.clientWidth
};
//get tooltip dimensions
var obj = {
'width': tip.offsetWidth,
'height': tip.offsetHeight
};
//set tooltip position
var x = cont.offsetX, y = cont.offsetY;
style.top = ((pos.y + y + obj.height > win.height)?
(pos.y - obj.height - y) : pos.y + y) + 'px';
style.left = ((pos.x + obj.width + x > win.width)?
(pos.x - obj.width - x) : pos.x + x) + 'px';
}
As you can see the onShow function is called from the onMouseMove function. And after onShow, setTooltipPosition function is called which sets the style.display back to ' ' (see my comment in code). Because of this the tip is not being hidden even after calling hide() from onShow function.
When I commented out that line in setTooltipPosition, it worked i.e. tool tips were hidden for nodes which were hidden.
Is this a bug in infovis or am I doing something wrong. I would like to know how is hide function supposed to be used if its not a bug.
Also, does anyone know of any other better way to hide a tool tip?
I had a similar problem. The solution was to use the .css('visibility', 'visible') instead of .hide() - because the element was hidden to start with using css styling.
Related
The below is the source code of a simple parallax plugin:
/*
Plugin: jQuery Parallax
Version 1.1.3
Author: Ian Lunn
Twitter: #IanLunn
Author URL: http://www.ianlunn.co.uk/
Plugin URL: http://www.ianlunn.co.uk/plugins/jquery-parallax/
Dual licensed under the MIT and GPL licenses:
http://www.opensource.org/licenses/mit-license.php
http://www.gnu.org/licenses/gpl.html
*/
(function( $ ){
var $window = $(window);
var windowHeight = $window.height();
$window.resize(function () {
windowHeight = $window.height();
});
$.fn.parallax = function(xpos, speedFactor, outerHeight) {
var $this = $(this);
var getHeight;
var firstTop;
var paddingTop = 0;
//get the starting position of each element to have parallax applied to it
$this.each(function(){
firstTop = $this.offset().top;
});
if (outerHeight) {
getHeight = function(jqo) {
return jqo.outerHeight(true);
};
} else {
getHeight = function(jqo) {
return jqo.height();
};
}
// setup defaults if arguments aren't specified
if (arguments.length < 1 || xpos === null) xpos = "50%";
if (arguments.length < 2 || speedFactor === null) speedFactor = 0.1;
if (arguments.length < 3 || outerHeight === null) outerHeight = true;
// function to be called whenever the window is scrolled or resized
function update(){
var pos = $window.scrollTop();
$this.each(function(){
var $element = $(this);
var top = $element.offset().top;
var height = getHeight($element);
// Check if totally above or totally below viewport
if (top + height < pos || top > pos + windowHeight) {
return;
}
console.log(firstTop + " " + pos);
$this.css('backgroundPosition', xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px");
});
}
$window.bind('scroll', update).resize(update);
update();
};
})(jQuery);
Now suppose i call the plugin , like so , on multiple elements.
$('#intro').parallax("50%", .8);
$('#second').parallax("50%", 0.1);
$('.bg').parallax("50%", 0.4);
$('#third').parallax("50%", 0.3);
What am i really doing ? creating multiple instances of the plugin ?
A demo of the plugin itself can be seen HERE.
No, you are not creating multiple instances of the plugin.
What you are doing is that you are calling this function multiple times:
$.fn.parallax = function(xpos, speedFactor, outerHeight) {
This is perfectly fine to do.
What you really are looking at is a jQuery extension method. This method merges the contents of an object onto the jQuery prototype to provide new jQuery instance methods.
Whenever you see the fn property, you are looking at an alias to the prototype property of jQuery.
Lets examine some lines in the parallax script you are embedding:
$.fn.parallax = function(xpos, speedFactor, outerHeight) {
This line is the start of a new jQuery prototype extension method that takes three arguments
Here is a more simple example that extends jQuery with a new method
$(function () {
// declare the new method greenify
$.fn.greenify = function() {
// The element that this method is used on will have the color green by using jQuery .css();
this.css( "color", "green" );
};
// Then to use your brand new jQuery extension method simply do this
$( "a" ).greenify();
$('.myElem').greenify();
$('#someElemId').greenify();
});
What is happening is that we are using the same method and applying it to different elements in the dom.
I hope this made it clearer what is really going on and how extension methods work.
First question, so please let me know if I could/should be asking this more succinctly/clearly...
I'm working on an experimental script that vibrates objects with a given class with increasing intensity over time. I'm working with jRumble as a base. My script needs to get the initial CSS position of objects to be vibrated and then add the modifying var. I set this up like so at first:
$this.animate({
'left': parseFloat($this.css( "left" )) + rx + 'px',
which works great in Chrome, but Safari and iOS return different values. This explained the problem: jquery $('selector').css('top') returns different values for IE, firefox and Chrome, so I switched to using .offset:
offset = $(".offreg").offset();
...
'left': offset.left + rx + 'px',
but this is problematic because the script iterates-- .offset returns the current position each time instead of the initial one. I tried placing the var outside of the iterating section, but this (obviously) doesn't work because it is no longer associated with the "this" of the rumble script. The var is then set at the value of the first element with class .offreg.
The page itself is here: http://sallymaier.github.io/off-register/
I'm going to revert to the last version that worked in Chrome. My github is public, so you can see my mess-making there I think.
Full script as it works in Chrome below:
var offRegister = function() {
if (!($ = window.jQuery)) { // see if jQuery is already called, if not, calling script
script = document.createElement( 'script' );
script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js';
script.onload=runRumbler;
document.body.appendChild(script);
}
else {
runRumbler();
}
function runRumbler() {
$(document).ready(function(){
var count = 0;
$(function addone(){
count = count+1;
(function($) {
$.fn.jrumble = function(options){ // jRumble by Jack Rugile. http://jackrugile.com/jrumble/
/*========================================================*/
/* Options
/*========================================================*/
var defaults = {
x: count/5,
y: count/5,
rotation: 0,
speed: 200,
opacity: false,
opacityMin: .5
},
opt = $.extend(defaults, options);
return this.each(function(){
/*========================================================*/
/* Variables
/*========================================================*/
var $this = $(this),
x = opt.x*2,
y = opt.y*2,
rot = opt.rotation*2,
speed = (opt.speed === 0) ? 1 : opt.speed,
opac = opt.opacity,
opacm = opt.opacityMin,
inline,
interval;
/*========================================================*/
/* Rumble Function
/*========================================================*/
var rumbler = function(){
var rx = Math.floor(Math.random() * (x+1)) -x/2,
ry = Math.floor(Math.random() * (y+1)) -y/2,
rrot = Math.floor(Math.random() * (rot+1)) -rot/2,
ropac = opac ? Math.random() + opacm : 1;
/*========================================================*/
/* Ensure Movement From Original Position
/*========================================================*/
rx = (rx === 0 && x !== 0) ? ((Math.random() < .5) ? 1 : -1) : rx;
ry = (ry === 0 && y !== 0) ? ((Math.random() < .5) ? 1 : -1) : ry;
/*========================================================*/
/* Check Inline
/*========================================================*/
if($this.css('display') === 'inline'){
inline = true;
$this.css('display', 'inline-block');
}
/*========================================================*/
/* Rumble Element
/*========================================================*/
$this.animate({
'left': parseFloat($this.css( "left" )) + rx + 'px', // move from declared position
'top': parseFloat($this.css( "top" )) + ry+'px',
'-ms-filter':'progid:DXImageTransform.Microsoft.Alpha(Opacity='+ropac*100+')',
'filter':'alpha(opacity='+ropac*100+')',
'-moz-opacity':ropac,
'-khtml-opacity':ropac,
'opacity':ropac,
'-webkit-transform':'rotate('+rrot+'deg)',
'-moz-transform':'rotate('+rrot+'deg)',
'-ms-transform':'rotate('+rrot+'deg)',
'-o-transform':'rotate('+rrot+'deg)',
'transform':'rotate('+rrot+'deg)'
});
}; /* close rumble function */
/*========================================================*/
/* Rumble CSS Reset
/*========================================================*/
var reset = {
'left':0,
'top':0,
'-ms-filter':'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)',
'filter':'alpha(opacity=100)',
'-moz-opacity':1,
'-khtml-opacity':1,
'opacity':1,
'-webkit-transform':'rotate(0deg)',
'-moz-transform':'rotate(0deg)',
'-ms-transform':'rotate(0deg)',
'-o-transform':'rotate(0deg)',
'transform':'rotate(0deg)'
};
/*========================================================*/
/* Rumble Start/Stop Trigger
/*========================================================*/
$this.bind({
'startRumble': function(e){
e.stopPropagation();
clearInterval(interval);
interval = setInterval(rumbler, speed)
},
'stopRumble': function(e){
e.stopPropagation();
clearInterval(interval);
if(inline){
$this.css('display', 'inline');
}
$this.css(reset);
}
});
});// End return this.each
};// End $.fn.jrumble
})(jQuery);
/*===============================================================*/
/* Specify selector to vibrate below.
/* For bookmarklet, 'div' will vibrate all elements,
/* in plugin this can be specifically targeted to a class or id.
/*===============================================================*/
$('.offreg').jrumble();
$('.offreg').trigger('startRumble');
setTimeout(addone, 1000); // how many seconds to wait before adding to count, increasing vibration
}); // end addone()
});
};
};
offRegister();
I've been playing around with this source code the last couple minutes, and it seems to me the problem stems from the use of position: absolute on the rumbling elements themselves. In the jRumble docs http://jackrugile.com/jrumble/#documentation it says that 'For rumble elements that are position fixed/absolute, they should instead be wrapped in an element that is fixed/absolute'.
So if you set your container element to position: absolute and then position all the interior divs relative to the container, you'll no longer need to alter the jRumble source code and redefine the plugin every iteration to change the default position or compute the offset of each element - all you'll have to do is increment the x and y values in the jRumble call itself.
The code below is UNTESTED, but I think you want something closer to this (again, this all hinges on the .offreg elements being position: relative):
$(document).ready(function(){
var count = 0;
function addone(){
count++;
$('.offreg').jrumble({
x: count,
y: count,
rotation: 1
});
$('.offreg').trigger('startRumble');
setTimeout(addone, 1000);
}
});
I like Sean's answer of sidestepping the need for adding code to override $.fn.jrumble, which creates a brittle maintenance point and makes $.fn.jrumble unusable outside this code block (since it depends on the variable "count").
For the immediate issue of the variable scope, if you still need to solve that problem, I'd suggest moving the redefinition of $.fn.jrumble out of addone(), and then scoping a variable, "offset", outside of said redefinition. Then refer to that variable in your call to $this.animate(). Code is below, but again, Sean's answer should make this unnecessary.
var offRegister = function() {
if (!($ = window.jQuery)) { // see if jQuery is already called, if not, calling script
script = document.createElement( 'script' );
script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js';
script.onload=runRumbler;
document.body.appendChild(script);
}
else {
runRumbler();
}
};
function runRumbler() {
$(document).ready(function(){
var count = 0;
var offset;
function addone() {
...
$('.offreg').jrumble();
$('.offreg').trigger('startRumble');
offset = $('.offreg').offset();
setTimeout(addone, 1000); // how many seconds to wait before adding to
}
(function($){
$.fn.jrumble = function(options){
...
$this.animate({
'left': parseFloat(offset) + rx + 'px',
...
});
...
};
)(jQuery);
...
}
offRegister();
Another suggestion: make the whole thing an anonymous function. Instead of:
var offRegister = function() {
...
}
offRegister();
Try:
(function(){
...
})();
That way you don't stick a variable "offRegister" in the global namespace. In this case, doing so isn't a problem, but in general code that you want to reuse elsewhere is cleaner if it doesn't introduce new globals.
I am trying to scroll by highlighting text and dragging down. Now, as you are probably aware, this is standard, default behavior for a standard overflow: auto element, however I am trying to do it with some fancy scrollbars courtesy of jQuery jScrollPane by Kelvin Luck.
I have created a fiddle here: DEMO
basically as you can see, highlighting and scrolling works in the top box (the default overflow: auto box) but in the second it doesn't and, to compound matters, once you reach the bottom it INVERTS your selection!
So, my question(s) is(are) this(these): is there a way to fix this? If so, how?
UPDATE
I have been working on this quite a bit and have found a slight solution using setTimeout()
however, it doesn't work as intended and if anybody is willing to help I have forked it to a new fiddle here: jsFiddle
the code itself is:
pane = $('#scrolldiv2');
pane.jScrollPane({animateEase: 'linear'});
api = pane.data('jsp');
$('#scrolldiv2').on('mousedown', function() {
$(this).off().on('mousemove', function(e) {
rel = $(this).relativePosition();
py = e.pageY - rel.y;
$t = $(this);
if (py >= $(this).height() - 20) {
scroll = setTimeout(scrollBy, 400, 20);
}
else if (py < 20) {
scroll = setTimeout(scrollBy, 400, -20);
}
else {
clearTimeout(scroll);
}
})
}).on('mouseup', function() {
$(this).off('mousemove');
clearTimeout(scroll);
})
var scrollBy = function(v) {
if (api.getContentPositionY < 20 & v == -20) {
api.scrollByY(v + api.getContentPositionY);
clearTimeout(scroll);
} else if (((api.getContentHeight - $t.height()) - api.getContentPositionY) < 20 & v == 20) {
api.scrollByY((api.getContentHeight - $t.height()) - api.getContentPositionY);
clearTimeout(scroll);
} else {
api.scrollByY(v, true)
scroll = setTimeout(scrollBy, 400, v)
}
}
$.fn.extend({
relativePosition: function() {
var t = this.get(0),
x, y;
if (t.offsetParent) {
x = t.offsetLeft;
y = t.offsetTop;
while ((t = t.offsetParent)) {
x += t.offsetLeft;
y += t.offsetTop;
}
}
return {
x: x,
y: y
}
},
})
You just have to scroll down/up depending on how close the mouse is to the end of the div; is not as good as the native solution but it gets the job done ( http://jsfiddle.net/PWYpu/25/ )
$('#scrolldiv2').jScrollPane();
var topScroll = $('#scrolldiv2').offset().top,
endScroll = topScroll + $('#scrolldiv2').height(),
f = ($('#scrolldiv2').height() / $('#scrolldiv2 .jspPane').height())*5 ,
selection = false,
_prevY;
$(document).mousemove(function(e){
var mY;
var delta = _prevY - e.pageY;
if((e.pageY < endScroll && (mY = ((e.pageY - endScroll + 80)/f)) > 0) ||
(e.pageY > topScroll && (mY = (e.pageY - (topScroll + 80))/f) < 0)){
if(selection && (delta > 10 || delta < -10) )
$('#scrolldiv2').data('jsp').scrollByY(mY, false) ;
}
})
$('#scrolldiv2').mousedown(function(e){_prevY = e.pageY; selection = true ;})
$(window).mouseup(function(){selection = false ;})
BTW, the reason it inverts the selection is because it reached the end of the document, just put some white space down there and problem solved.
I really hate to say it, I know it's an issue even I ran into with the update to this plugin, but in the old plugin (seen here) it works just fine with basic call. So I just reverted my copy.
Need help to newbie. I have a list elements inside scrollable div(tiny scrollbar) with different background colors: red and blue. Also i have a two square divs with red and blue background colors.
ToDo: add class 'border' to the blue square div, when list is scrolled to the first blue colored element.
Here's example: http://jsfiddle.net/uy4hK/19/
I guess there should be something like a position trigger for different colored list elements. Need help!
You may customize the plugin to support scrolling events. Modify the whell and drag functions as below:
function wheel(oEvent) {
if (!(oContent.ratio >= 1)) {
oEvent = $.event.fix(oEvent || window.event);
var iDelta = oEvent.wheelDelta ? oEvent.wheelDelta / 120 : -oEvent.detail / 3;
iScroll -= iDelta * options.wheel;
iScroll = Math.min((oContent[options.axis] - oViewport[options.axis]), Math.max(0, iScroll));
oThumb.obj.css(sDirection, iScroll / oScrollbar.ratio);
oContent.obj.css(sDirection, -iScroll);
oEvent.preventDefault();
// New code
if (options.onScroll && typeof (options.onScroll) == "function") {
options.onScroll.call(this);
}
};
};
function drag(oEvent) {
if (!(oContent.ratio >= 1)) {
iPosition.now = Math.min((oTrack[options.axis] - oThumb[options.axis]), Math.max(0, (iPosition.start + ((sAxis ? oEvent.pageX : oEvent.pageY) - iMouse.start))));
iScroll = iPosition.now * oScrollbar.ratio;
oContent.obj.css(sDirection, -iScroll);
oThumb.obj.css(sDirection, iPosition.now);
// New code
if (options.onScroll && typeof (options.onScroll) == "function") {
options.onScroll.call(this);
}
}
return false;
};
Then you can pass a custom function that will be executed on scrolling:
$(function () {
var fisrtBlueOffset = $(".overview li.blue:first").offset().top;
var viewportHeight = $(".viewport").height();
$('#scrollbar1').tinyscrollbar({
"onScroll": function () {
var viewportTop = parseInt($(".overview").css("top"));
if (fisrtBlueOffset + viewportTop < viewportHeight) {
$(".blue-block").css("border", "1px solid #000");
}
else {
$(".blue-block").css("border", "");
}
}
});
});
I want to get the position of an element relative to the browser's viewport (the viewport in which the page is displayed, not the whole page). How can this be done in JavaScript?
Many thanks
The existing answers are now outdated. The native getBoundingClientRect() method has been around for quite a while now, and does exactly what the question asks for. Plus it is supported across all browsers (including IE 5, it seems!)
From MDN page:
The returned value is a TextRectangle object, which contains read-only left, top, right and bottom properties describing the border-box, in pixels, with the top-left relative to the top-left of the viewport.
You use it like so:
var viewportOffset = el.getBoundingClientRect();
// these are relative to the viewport, i.e. the window
var top = viewportOffset.top;
var left = viewportOffset.left;
On my case, just to be safe regarding scrolling, I added the window.scroll to the equation:
var element = document.getElementById('myElement');
var topPos = element.getBoundingClientRect().top + window.scrollY;
var leftPos = element.getBoundingClientRect().left + window.scrollX;
That allows me to get the real relative position of element on document, even if it has been scrolled.
var element = document.querySelector('selector');
var bodyRect = document.body.getBoundingClientRect(),
elemRect = element.getBoundingClientRect(),
offset = elemRect.top - bodyRect.top;
Edit: Add some code to account for the page scrolling.
function findPos(id) {
var node = document.getElementById(id);
var curtop = 0;
var curtopscroll = 0;
if (node.offsetParent) {
do {
curtop += node.offsetTop;
curtopscroll += node.offsetParent ? node.offsetParent.scrollTop : 0;
} while (node = node.offsetParent);
alert(curtop - curtopscroll);
}
}
The id argument is the id of the element whose offset you want. Adapted from a quirksmode post.
jQuery implements this quite elegantly. If you look at the source for jQuery's offset, you'll find this is basically how it's implemented:
var rect = elem.getBoundingClientRect();
var win = elem.ownerDocument.defaultView;
return {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset
};
function inViewport(element) {
let bounds = element.getBoundingClientRect();
let viewWidth = document.documentElement.clientWidth;
let viewHeight = document.documentElement.clientHeight;
if (bounds['left'] < 0) return false;
if (bounds['top'] < 0) return false;
if (bounds['right'] > viewWidth) return false;
if (bounds['bottom'] > viewHeight) return false;
return true;
}
source
The function on this page will return a rectangle with the top, left, height and width co ordinates of a passed element relative to the browser view port.
localToGlobal: function( _el ) {
var target = _el,
target_width = target.offsetWidth,
target_height = target.offsetHeight,
target_left = target.offsetLeft,
target_top = target.offsetTop,
gleft = 0,
gtop = 0,
rect = {};
var moonwalk = function( _parent ) {
if (!!_parent) {
gleft += _parent.offsetLeft;
gtop += _parent.offsetTop;
moonwalk( _parent.offsetParent );
} else {
return rect = {
top: target.offsetTop + gtop,
left: target.offsetLeft + gleft,
bottom: (target.offsetTop + gtop) + target_height,
right: (target.offsetLeft + gleft) + target_width
};
}
};
moonwalk( target.offsetParent );
return rect;
}
You can try:
node.offsetTop - window.scrollY
It works on Opera with viewport meta tag defined.
I am assuming an element having an id of btn1 exists in the web page, and also that jQuery is included. This has worked across all modern browsers of Chrome, FireFox, IE >=9 and Edge.
jQuery is only being used to determine the position relative to document.
var screenRelativeTop = $("#btn1").offset().top - (window.scrollY ||
window.pageYOffset || document.body.scrollTop);
var screenRelativeLeft = $("#btn1").offset().left - (window.scrollX ||
window.pageXOffset || document.body.scrollLeft);
Thanks for all the answers. It seems Prototype already has a function that does this (the page() function). By viewing the source code of the function, I found that it first calculates the element offset position relative to the page (i.e. the document top), then subtracts the scrollTop from that. See the source code of prototype for more details.
Sometimes getBoundingClientRect() object's property value shows 0 for IE. In that case you have to set display = 'block' for the element. You can use below code for all browser to get offset.
Extend jQuery functionality :
(function($) {
jQuery.fn.weOffset = function () {
var de = document.documentElement;
$(this).css("display", "block");
var box = $(this).get(0).getBoundingClientRect();
var top = box.top + window.pageYOffset - de.clientTop;
var left = box.left + window.pageXOffset - de.clientLeft;
return { top: top, left: left };
};
}(jQuery));
Use :
var elementOffset = $("#" + elementId).weOffset();
Based on Derek's answer.
/**
* Gets element's x position relative to the visible viewport.
*/
function getAbsoluteOffsetLeft(el) {
let offset = 0;
let currentElement = el;
while (currentElement !== null) {
offset += currentElement.offsetLeft;
offset -= currentElement.scrollLeft;
currentElement = currentElement.offsetParent;
}
return offset;
}
/**
* Gets element's y position relative to the visible viewport.
*/
function getAbsoluteOffsetTop(el) {
let offset = 0;
let currentElement = el;
while (currentElement !== null) {
offset += currentElement.offsetTop;
offset -= currentElement.scrollTop;
currentElement = currentElement.offsetParent;
}
return offset;
}
Here is something for Angular2 +. Tested on version 13
event.srcElement.getBoundingClientRect().top;