elementFromPoint returns null after scrolling the page - javascript

I have a javascript bookmarklet I put together to make an arduous task a little more bearable. Essentially I am going through hundreds of pages of training material and making sure that all of it has been properly swapped from Helvetica to Arial. The bookmarklet code is below, but a quick breakdown is that it creates a mousemove event listener and a small, absolutely positioned div. On mousemove events, the div moves to the new mouse position (offset by 10px down and right), gets the element under the mouse with elementFromPoint and shows the font-family property for that element. oh and it changes it's background color based on whether Arial appears within the property.
var bodyEl=document.getElementsByTagName("body")[0];
var displayDiv=document.createElement("div");
displayDiv.style.position="absolute";
displayDiv.style.top="0px";
displayDiv.style.top="0px";
bodyEl.appendChild(displayDiv);
function getStyle(el,styleProp) {
var camelize = function (str) {
return str.replace(/\-(\w)/g, function(str, letter){
return letter.toUpperCase();
});
};
if (el.currentStyle) {
return el.currentStyle[camelize(styleProp)];
} else if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el,null)
.getPropertyValue(styleProp);
} else {
return el.style[camelize(styleProp)];
}
}
function getTheElement(x,y) {return document.elementFromPoint(x,y);}
fn_displayFont=function displayFont(e) {
e = e || window.event;
var divX=e.pageX+10;
var divY=e.pageY+10;
var font=getStyle(getTheElement(e.pageX,e.pageY),"font-family");
if (font.toLowerCase().indexOf("arial") != -1) {
displayDiv.style.backgroundColor = "green";
} else {
displayDiv.style.backgroundColor = "red";
}
displayDiv.style.top= divY.toString() + "px";
displayDiv.style.left= divX.toString() + "px";
displayDiv.style.fontFamily=font;
displayDiv.innerHTML=font;
}
window.addEventListener('mousemove', fn_displayFont);
document.onkeydown = function(evt) {
evt = evt || window.event;
if (evt.keyCode == 27) {
window.removeEventListener('mousemove', fn_displayFont);
bodyEl.removeChild(displayDiv);
}
};
(for the record, I stole the style determining code from an answer here on SO, but I lost the tab not long after. Thanks, anonymous internet guy!)
So this all works great - UNTIL I try to hover over a part of the page that is scrolled down from the top. The div sits at where it would be if I had the mouse on the very bottom of the screen while scrolled to the top of the page, and if I scroll down far enough firebug starts logging that e.pageX is undefined.
Any ideas?

Alrighty then, figured it out. I saw http://www.daniweb.com/web-development/javascript-dhtml-ajax/threads/276742/elementfrompoint-problems-when-window-has-been-scrolled- and thought it meant I had to minus the pageoffset straight away from the e.pageX/Y values, before I used it to calculate the div position or anything else, this just broke everything for me so I assumed it must have been unrelated - not so!
From what I now understand the elementFromPoint method takes a point relative in the current view of the browser, which is to say, base on the top left corner of what can currently be seen, not the page as a whole. I fixed it by just taking the offset from the X and Y values when I was getting the element. The now-working code is below.
var bodyEl=document.getElementsByTagName("body")[0];
var displayDiv=document.createElement("div");
displayDiv.style.position="absolute";
displayDiv.style.top="0px";
displayDiv.style.top="0px";
bodyEl.appendChild(displayDiv);
function getStyle(el,styleProp) {
var camelize = function (str) {
return str.replace(/\-(\w)/g, function(str, letter){
return letter.toUpperCase();
});
};
if (el.currentStyle) {
return el.currentStyle[camelize(styleProp)];
} else if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el,null)
.getPropertyValue(styleProp);
} else {
return el.style[camelize(styleProp)];
}
}
function getTheElement(x,y) {return document.elementFromPoint(x,y);}
fn_displayFont=function displayFont(e) {
e = e || window.event;
var divX=e.pageX + 10;
var divY=e.pageY + 10;
var font=getStyle(getTheElement(e.pageX - window.pageXOffset,e.pageY - window.pageYOffset),"font-family");
if (font.toLowerCase().indexOf("arial") != -1) {
displayDiv.style.backgroundColor = "green";
} else {
displayDiv.style.backgroundColor = "red";
}
displayDiv.style.top= divY.toString() + "px";
displayDiv.style.left= divX.toString() + "px";
displayDiv.style.fontFamily=font;
displayDiv.innerHTML=font;
}
document.addEventListener('mousemove', fn_displayFont);
document.onkeydown = function(evt) {
evt = evt || window.event;
if (evt.keyCode == 27) {
window.removeEventListener('mousemove', fn_displayFont);
bodyEl.removeChild(displayDiv);
}
};

Hmm instead of checking with the mouse, why not just check every leaf node? If any leaf node has a font-family of arial, then it should indicate that one of its ancestors has a font-family of Arial.
First you need to get jquery onto the page. Try this bookmarklet
Then run this code:
(function(){
var arialNodes = $('div:not(:has(*))').filter(function(){
return $(this).css('font-family').toLowerCase().indexOf("arial") != -1;
});
})();
The arialNodes variable should contain every leaf node that has a font-family of 'Arial'. You can then use this to figure out which parent element has the declaration.
Or if you just want to see if a page is compliant or not, just check the length.
Updated
Updated to reflect comments below
(function() {
var arialNodes = $('*:not(:has(*))', $('body')).filter(function() {
return $(this).css('font-family').toLowerCase().indexOf("arial") === -1;
});
var offendingParents = [];
arialNodes.each(function(){
var highestOffendingParent = $(this).parentsUntil('body').filter(function(){
return $(this).css('font-family').toLowerCase().indexOf("arial") === -1;
}).last();
if(offendingParents.indexOf(highestOffendingParent) === -1){
offendingParents.push(highestOffendingParent);
}
});
})();

Related

if else statement javascript to make div change color based on .scrollTop

I am a beginner coder and I'm trying to change the background color of a div based on how far down scrolled the webpage is, where am I going wrong?
Do I need to put in something to call the scrollTop amount?
(function () {
var scroll = .scrollTop;
if (scroll > 50) {
document.getElementByClassName("shop").style.backgroundColor = '#99C262';
}
else
{
document.getElementByClassName("shop").style.backgroundColor = ‘red’;
}
})();
Lose the dot, instead of “.shop” go for “shop”
And the actual function getElementsByClassName and it returns a collections of divs with the class name. But you need the first one (assuming you have only one such div) hence the 0 index in array
parentDOM.getElementsByClassName('test')[0].style.backgroundColor = "red";
<p class="test">hello here</p>
Many errors in the code....
var scrole = .scrollTop; not sure if you are trying to assign any value or is it window.scrollTop.
document.getElementByClassName(".shop") should be changed to document.getElementByClassName("shop")
else if (scroll < 50 ){ //statement } to be changed to else { //statement }
function is wrapped in small bracket but has never been invoked.
Self invoking function example :-
(function(){
console.log(Math.PI);
})();
Using a dot before a class name when getting it in javascript using getElementsByClassName is an Incorrect Syntax. Below is the correct syntax.
document.getElementsByClassName("shop")
Tip: Always Use the console window to monitor your Syntax and other errors.
Your code are only executed once if you don't attach a listener. Let me just use these code from MDN docs with a bit of edit. The docs
let last_known_scroll_position = 0;
let ticking = false;
function doSomething(scroll_pos) {
if (scroll_pos > 50) {
document.querySelector(“.shop”).style.backgroundColor = '#99C262';
} else if (scroll_post < 50) {
document.querySelecttor(“.shop”).style.backgroundColor = ‘red’;
} else {
// add more colorrrs!
}
}
window.addEventListener('scroll', function(e) {
last_known_scroll_position = window.scrollY;
if (!ticking) {
window.requestAnimationFrame(function() {
doSomething(last_known_scroll_position);
ticking = false;
});
ticking = true;
}
});

How to catch clicks on multiple images?

I have an iOS uiwebview with multiple imagemaps that I need to catch clicks on, so I can handle scaling on different iOS devices. The click handler I install works on the first image, but not on subsequent images. How do I make the click handler work on multiple images? The relevant code is below:
$.fn.imageMapSetup2 = function () {
$('img').each(function () {
if (typeof ($(this).attr('usemap')) == 'undefined') {
return;
}
var img = $(this);
// add click handler
img.on('click', function (event) {
img.imgClick(event);
});
});
};
$.fn.imgClick = function (mouseDown) {
mouseDown.preventDefault();
var $img = this;
var map = $img.attr('usemap').replace('#', '');
$('map[name="' + map + '"]').find('area').each(function () {
var $this = $(this),
coords = $this.attr('coords').split(',');
// lots of scaling code omitted
if (mouseX >= left && mouseX <= right &&
mouseY >= top && mouseY <= bottom) {
window.location = $this.attr('href');
}
});
};
FYI I have debugged the code in Safari and function imgClick() is not getting called for the second and subsequent images.
Add a click event listener to the parent element of the images. This could be the body element. Pass the event as an argument. Then, check the event, and use that variable to make changes to your image.
document.addEventListener("click", function (event) {
if (!event.target.tagName === "img") return;
if (typeof event.target.getAttribute("usemap") == "undefined") {
return;
}
imgClick(event);
});

Touch move fire only once when implementing "less" like scrolling on mobile

I'm trying to to implement touch scroll in less extension to jQuery Terminal. It work similar to less unix command.
I have this code:
self.touch_scroll(function(event) {
// how much difference changed since last touch move
var delta = event.current.clientY - event.previous.clientY;
var ret;
var interpreter = interpreters.top();
if (is_function(interpreter.touchscroll)) {
ret = interpreter.touchscroll(event, delta, self);
} else if (is_function(settings.touchscroll)) {
ret = settings.touchscroll(event, delta, self);
}
if (ret === true) {
return;
}
return false;
});
// make_callback_plugin is helper that use $.Callbacks and make sure that there is only
// one handler on the element
$.fn.touch_scroll = make_callback_plugin({
name: 'touch',
init: function(handler) {
var origin;
var previous;
$(this).on('touchstart.scroll', function(e) {
e = e.originalEvent;
if (e.touches.length === 1) {
previous = origin = e.touches[0];
}
}).on('touchmove.scroll', function(e) {
e = e.originalEvent;
console.log(!!origin + ' && ' + (e.touches.length) + ' === 1');
if (origin && e.touches.length === 1) {
var current = e.touches[0];
var ret = handler({
origin: origin,
previous: previous,
current: current
});
if (ret === false) {
// this don't change anything
e.preventDefault();
}
previous = current;
}
}).on('touchend.scroll', function() {
if (origin || previous) {
origin = previous = null;
}
});
},
destroy: function() {
$(this).off('touchstart.scroll touchmove.scroll touchend.scroll');
}
});
and inside less I have:
function scroll(delta, scroll_by) {
if (delta > 0) {
pos -= scroll_by;
if (pos < 0) {
pos = 0;
}
} else {
pos += scroll_by;
if (pos - 1 > lines.length - rows) {
pos = lines.length - rows + 1;
}
}
print();
return true;
}
term.push($.noop, {
onResize: refresh_view,
touchscroll: function(event, delta) {
console.log({delta});
var offset = Math.abs(delta);
// 14 is default height of single line in pixels
scroll(delta, Math.ceil(offset / 14));
return false;
},
mousewheel: function(event, delta) {
return scroll(delta, scroll_by);
},
I also have this css:
.terminal-less {
touch-action: none;
overscroll-behavior-y: contain;
}
on Mousewheel scrolling works good it scroll with the same amount of scroll_by which is by default 3 (seems about right). (pos is lines offset so if I use pos++ it move/scroll by one line, delta in touchscroll is positive or negative from about -20 to 20 pixels.
The problem I have and the question is, how can I make it scroll with the finger? it don't feel right. Also it scroll only once it don't move with the finger. touchmove fire only once, shoudn't it fire while I move the finger while touching the phone?
Anyone have experience with this type of touch scroll behavior?
I was searching for similar problem and didn't found solution. Do you know why touchmove could fire once? The only thing I can think of was textarea that is used as clipboard (on mobile it's also used to enable virtual keyboard), but I've set background to red and it don't move on Android. I was testing other code from this drawing demo:
https://zipso.net/a-simple-touchscreen-sketchpad-using-javascript-and-html5/
and it works fine, touch move keeps firing while you move the finger.
Any ideas? It will be hard to replicate but if somone is interested in investigation I can put all my code on github in jQuery Terminal repo (in some branch).
What's weird is that touchend don't fire after touchmove, it fire once only when I click on the terminal to enable keyboard.
I've tried to monkey patch jQuery on and log each time it fire but I didn't have any other event (that may prevent default behavior) also according to docs mouse events fire after touchend and those don't fire.
What you need is simple .terminal-wrapper { pointer-events: none; } (based on the devel branch). But with this rule you can't select the text, that's why you need to use it only for mobile devices.
I'm not sure if this will block the selection of text on mobile, but if so, you can try to add this on touchstart (or even on touchmove as the first instruction) and remove it on touchend.
Also, I had to add some JS code, because without it interpreter.touchScroll is undefined. But this is not the main cause of the problem.
interpreters = new Stack($.extend({}, settings.extra, {
name: settings.name,
prompt: prompt,
keypress: settings.keypress,
keydown: settings.keydown,
resize: settings.onResize,
greetings: settings.greetings,
mousewheel: settings.mousewheel,
touchScroll: settings.touchScroll, // NEW LINE
history: settings.history,
keymap: new_keymap
}, interpreter));
self.touch_scroll(function(event) {
var delta = event.current.clientY - event.previous.clientY;
var ret;
var interpreter = interpreters.top(); // NEW LINE
if (is_function(interpreter.touchScroll)) {
ret = interpreter.touchScroll(event, delta, self);
} else if (is_function(settings.touchScroll)) {
ret = settings.touchScroll(event, delta, self);
}
if (ret === true) {
return;
}
});
Without pointer-events: none;
With pointer-events: none;

Check if mouse is inside div

I want to use an if statement to check if the mouse is inside a certain div, something like this:
if ( mouse is inside #element ) {
// do something
} else {
return;
}
This will result in the function to start when the mouse is inside #element, and stops when the mouse is outside #element.
you can register jQuery handlers:
var isOnDiv = false;
$(yourDiv).mouseenter(function(){isOnDiv=true;});
$(yourDiv).mouseleave(function(){isOnDiv=false;});
no jQuery alternative:
document.getElementById("element").addEventListener("mouseenter", function( ) {isOnDiv=true;});
document.getElementById("element").addEventListener("mouseout", function( ) {isOnDiv=false;});
and somewhereelse:
if ( isOnDiv===true ) {
// do something
} else {
return;
}
Well, that's kinda of what events are for. Simply attach an event listener to the div you want to monitor.
var div = document.getElementById('myDiv');
div.addEventListener('mouseenter', function(){
// stuff to do when the mouse enters this div
}, false);
If you want to do it using math, you still need to have an event on a parent element or something, to be able to get the mouse coordinates, which will then be stored in an event object, which is passed to the callback.
var body = document.getElementsByTagName('body');
var divRect = document.getElementById('myDiv').getBoundingClientRect();
body.addEventListener('mousemove', function(event){
if (event.clientX >= divRect.left && event.clientX <= divRect.right &&
event.clientY >= divRect.top && event.clientY <= divRect.bottom) {
// Mouse is inside element.
}
}, false);
But it's best to use the above method.
simply you can use this:
var element = document.getElementById("myId");
if (element.parentNode.querySelector(":hover") == element) {
//Mouse is inside element
} else {
//Mouse is outside element
}
Improving using the comments
const element = document.getElementById("myId");
if (element.parentNode.matches(":hover")) {
//Mouse is inside element
} else {
//Mouse is outside element
}
$("div").mouseover(function(){
//here your stuff so simple..
});
You can do something like this
var flag = false;
$("div").mouseover(function(){
flag = true;
testing();
});
$("div").mouseout(function(){
flag = false;
testing();
});
function testing(){
if(flag){
//mouse hover
}else{
//mouse out
}
}

modify hoverIntent to handle touch events on mobiles

Good day all.
I'm having some problems with hoverintent.js a jquery plugin that handle the mouseOver events in a different way than normal.
Due to some complications, I can't modifiy anything but the js of this plugin, but I need to make it compliant with touch events and not only with mouseOver and mouseLeave.
after some debugs, I have managed to recognize this part of the code to be the one to modify:
var handleHover = function(e) {
// next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
if ( p == this ) { return false; }
// copy objects to be passed into t (required for event object to be passed in IE)
var ev = jQuery.extend({},e);
var ob = this;
// cancel hoverIntent timer if it exists
if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
// else e.type == "onmouseover"
if (e.type == "mouseover") {
// set "previous" X and Y position based on initial entry point
pX = ev.pageX; pY = ev.pageY;
// update "current" X and Y position based on mousemove
$(ob).bind("mousemove",track);
// start polling interval (self-calling timeout) to compare mouse coordinates over time
if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
// else e.type == "onmouseout"
} else {
// unbind expensive mousemove event
$(ob).unbind("mousemove",track);
// if hoverIntent state is true, then call the mouseOut function after the specified delay
if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
}
}
};
// bind the function to the two event listeners
return this.mouseover(handleHover).mouseout(handleHover);
what I've done so far is to make the function working different with mobiles:
var handleHover = function(e) {
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if(isMobile){
console.log("Ismobile");
}else{
... Same code as before here ...
}
// bind the function to the two event listeners
return this.mouseover(handleHover).mouseout(handleHover);
and now i'm struck. I would like it to "change" its behavior to handle the touch, and not the mouse over event, so on mobiles I will need to touch the element, instead to hovering on it. May someone give me an help? Am I on the right way? Is it the right way to think of it?
unluckily I have only the possibility to change this file and some few more.
Recently i bumped into several problems with changing hoverIntent.js, and ended up in writing my own plugin: hoverDelay.js (much simpler, and less code). see if you can use it, and modify it to your own needs (and maybe contribute the mobile code to it :-)

Categories

Resources