What is the most reliable and efficient way to find all elements having a scroll on a page?
Currently, I'm thinking about using element.all() with filter() comparing the height and scrollHeight attribute values:
element.all(by.xpath("//*")).filter(function (elm) {
return protractor.promise.all([
elm.getAttribute("height"),
elm.getAttribute("scrollHeight")
]).then(function (heights) {
return heights[1] > heights[0];
});
});
But I'm not sure about the correctness and performance of this approach.
This works with both horizontal and vertical scrollbars. The trick is detecting BOTH the too-wide/too-short AND if the computed CSS is going to allow you to display a scrollbar.
var ElementsWithScrolls = (function() {
var getComputedStyle = document.body && document.body.currentStyle ? function(elem) {
return elem.currentStyle;
} : function(elem) {
return document.defaultView.getComputedStyle(elem, null);
};
function getActualCss(elem, style) {
return getComputedStyle(elem)[style];
}
function isXScrollable(elem) {
return elem.offsetWidth < elem.scrollWidth &&
autoOrScroll(getActualCss(elem, 'overflow-x'));
}
function isYScrollable(elem) {
return elem.offsetHeight < elem.scrollHeight &&
autoOrScroll(getActualCss(elem, 'overflow-y'));
}
function autoOrScroll(text) {
return text == 'scroll' || text == 'auto';
}
function hasScroller(elem) {
return isYScrollable(elem) || isXScrollable(elem);
}
return function ElemenetsWithScrolls() {
return [].filter.call(document.querySelectorAll('*'), hasScroller);
};
})();
ElementsWithScrolls();
It will select the elements with overflowed and non-overflowed scrolls inside body tag:
$('body *').filter(function() {
return ($(this).scrollTop() != 0 || $(this).css('overflow') == 'scroll');
});
Related
I am trying to develop a wysiwyg editör.
In the editör, i am trying to find the position of "br" when onkeydown function fire.
<p><b>1234</b><br><br>678</p>
When i locate cursor near 6 getting 678 with "oSelection.anchorNode.nodeValue".
When i locate cursot near "br" getting nothing.
i want to find before and after tag near cursor?
Update 2: After talking to ismail the question could be changed to: how to find out, whether the element before/after the cursor is a <br> tag. This can be achieved like this:
var selection = window.getSelection(),
isBRBeforeCursor = IsBRBeforeCursor(selection),
isBRAfterCursor = IsBRAfterCursor(selection);
function GetPreviousSibling(node) {
if (node.previousSibling != null) {
return node.previousSibling;
} else if (node.parentNode != null) {
return GetPreviousSibling(node.parentNode);
} else {
return null;
}
}
function GetNextSibling(node) {
if (node.nextSibling != null) {
return node.nextSibling;
} else if (node.parentNode != null) {
return GetNextSibling(node.parentNode);
} else {
return null;
}
}
function IsBRBeforeCursor(selection) {
if(selection.anchorNode.nodeName === '#text') {
if(selection.anchorOffset > 0) {
// There is text before the cursor
return false;
} else {
var previousSibling = GetPreviousSibling(selection.anchorNode);
return previousSibling !== null && previousSibling.nodeName === 'BR';
}
} else {
if(selection.anchorOffset > 0) {
return selection.anchorNode.childNodes[selection.anchorOffset - 1].nodeName === 'BR';
} else {
var previousSibling = GetPreviousSibling(selection.anchorNode);
return previousSibling !== null && previousSibling.nodeName === 'BR';
}
}
}
function IsBRAfterCursor(selection) {
if(selection.anchorNode.nodeName === '#text') {
if(selection.anchorOffset < selection.anchorNode.nodeValue.length) {
// There is text after the cursor
return false;
} else {
var nextSibling = GetNextSibling(selection.anchorNode);
return nextSibling !== null && nextSibling.nodeName === 'BR';
}
} else {
if(selection.anchorNode.childNodes.length > selection.anchorOffset) {
return selection.anchorNode.childNodes[selection.anchorOffset].nodeName === 'BR';
} else {
var nextSibling = GetNextSibling(selection.anchorNode);
return nextSibling !== null && nextSibling.nodeName === 'BR';
}
}
}
Update: I think it is a bit tricky to always find the correct previous/next element, because the text is a node itself. So to get the previous/next element, you need to go a level up sometimes before looking left and right. Have a look at the following example:
<p><b>123</b><br><u><i><br>456</i></u><br></p>
Cursor is between 1 and 2.
The next element is <br>, which is one level up and then to the right.
Cursor is between 4 and 5.
The previous element is <br>, which is just one to the left.
The next element is <br>, which is two levels up and then to the right.
If this is the case, you can find the previous/next element like this:
function getElementBeforeSelection() {
var anchor = window.getSelection().anchorNode;
while(anchor.previousSibling === null && anchor.nodeName != 'BODY') {
anchor = anchor.parentNode;
}
return anchor.previousSibling;
}
Original answer: You can get to the surrounding elements with parentNode, previousSibling and nextSibling. So the tags before and after the cursor are:
var anchorNode = window.getSelection().anchorNode,
before = anchorNode.previousSibling,
after = anchorNode.nextSibling;
What I'm trying to do is find all 'section' elements, detect which one is in the viewport and apply a className to the current section. The className should be removed again when scrolling out of the viewport.
Here are the basics. I haven't included all methods and functions of the plugin, just what's needed to help answer the question:
// A simple forEach() implementation for Arrays, Objects and NodeLists.
// By Todd Motto
var forEach = function (collection, callback, scope) {
if (Object.prototype.toString.call(collection) === '[object Object]') {
for (var prop in collection) {
if (Object.prototype.hasOwnProperty.call(collection, prop)) {
callback.call(scope, collection[prop], prop, collection);
}
}
} else {
for (var i = 0, len = collection.length; i < len; i++) {
callback.call(scope, collection[i], i, collection);
}
}
};
// Determine if an element is the viewport or not
var _isInViewport = function (elem) {
var rect = elem.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
// Get all sections and
var _getSections = function () {
var sections = document.querySelectorAll('section');
forEach(sections, function (section) {
if (section._isInViewport) {
section(_isInViewport).classList.add('section-is-in-view');
alert('yest');
} else {
section(_isInViewport).classList.remove('not-in-view');
}
});
};
// The event handler
var _eventHandler = function (event) {
if (event.type === 'scroll') {
_getSections();
}
};
// Initialise the plugin
plugin.init = function (options) {
// Listen for scroll events and run event handler
document.addEventListener('scroll', _eventHandler, false);
}
Note: once it's working correctly, I plan to add some sort of debounce and throttler.
This immmediate thing that jumps out at me: this is wrong:
if (section._isInViewport) {
_isInViewport requires you to pass the element in as an argument, like so:
if (_isInViewport(section)) {
You also don't need to check the event type in the event handler. Since you're only calling that on scroll, you already know the event type is a scroll event. Instead of thing:
if (event.type === 'scroll') {
_getSections();
}
You want this:
_getSections();
I'm using a Owl Carousel slider.
When you reach the last or first item of the slider you can still drag the slider although there aren't anymore items. Instead there is a bounce effect like when you pull down a native mobile app to refresh the content.
Demo: (link removed since the official docs aren't there anymore)
Drag the slider to the right and it will bounce back.
Is there a possibility to disable that bounce effect?
Yes, you can disable the bounce effect. I struggled finding it but I did it.
Just remove or comment out this lines code in owl.carousel.js:
base.newPosX = Math.max(Math.min(base.newPosX, minSwipe()), maxSwipe());
if (base.browser.support3d === true) {
base.transition3d(base.newPosX);
} else {
base.css2move(base.newPosX);
}
E.g.
owl.carousel.js
function dragMove(event) {
var ev = event.originalEvent || event || window.event,
minSwipe,
maxSwipe;
base.newPosX = getTouches(ev).x - locals.offsetX;
base.newPosY = getTouches(ev).y - locals.offsetY;
base.newRelativeX = base.newPosX - locals.relativePos;
if (typeof base.options.startDragging === "function" && locals.dragging !== true && base.newRelativeX !== 0) {
locals.dragging = true;
base.options.startDragging.apply(base, [base.$elem]);
}
if ((base.newRelativeX > 8 || base.newRelativeX < -8) && (base.browser.isTouch === true)) {
if (ev.preventDefault !== undefined) {
ev.preventDefault();
} else {
ev.returnValue = false;
}
locals.sliding = true;
}
if ((base.newPosY > 10 || base.newPosY < -10) && locals.sliding === false) {
$(document).off("touchmove.owl");
}
minSwipe = function () {
return base.newRelativeX / 5;
};
maxSwipe = function () {
return base.maximumPixels + base.newRelativeX / 5;
};
// base.newPosX = Math.max(Math.min(base.newPosX, minSwipe()), maxSwipe());
// if (base.browser.support3d === true) {
// base.transition3d(base.newPosX);
// } else {
// base.css2move(base.newPosX);
// }
}
Hope it helps.
I'm working on this page. Main structure is some DIVs beneath each other. I need to do some transitions or animations when user scrolls from one to another. The height of the DIVs aren't the same. it is done only by min-height:100%. My JS doesn't work when I try to do any alert at the end of the DIV.
<div id="page">
<div class="section section_1"> ...content...</div>
<div class="section section_2">...content...</div>
<div class="section section_3">...content...</div>
<div class="section section_4">...content...</div>
</div>
This is the JS file
jQuery(
$('.section').on('scroll', function () {
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
alert('end of div');
}
})
);
Do you have any ideas why this doesn't work? Or can you suggest me any other solution how to make this kind of animation?
Try This:
jQuery(function($) {
$('.section').bind('scroll', function() {
if($(this).scrollTop() + $(this).innerHeight() >= this.scrollHeight) {
alert('end reached');
}
})
});
Code Example for Local
Question's already been answered here.
Edited
Bind your alert as such:
var shown = document.getElementById("page").children;
function callback () {
alert('end of div');
}
function isElementInViewport(el) {
var eap,
rect = el.getBoundingClientRect(),
docEl = document.documentElement,
vWidth = window.innerWidth || docEl.clientWidth,
vHeight = window.innerHeight || docEl.clientHeight,
efp = function (x, y) { return document.elementFromPoint(x, y) },
contains = "contains" in el ? "contains" : "compareDocumentPosition",
has = contains == "contains" ? 1 : 0x14;
// Return false if it's not in the viewport
if (rect.right < 0 || rect.bottom < 0
|| rect.left > vWidth || rect.top > vHeight)
return false;
// Return true if any of its four corners are visible
return (
(eap = efp(rect.left, rect.top)) == el || el[contains](eap) == has
|| (eap = efp(rect.right, rect.top)) == el || el[contains](eap) == has
|| (eap = efp(rect.right, rect.bottom)) == el || el[contains](eap) == has
|| (eap = efp(rect.left, rect.bottom)) == el || el[contains](eap) == has
);
}
function fireIfElementVisible (el, callback) {
return function () {
if ( isElementInViewport(el) ) {
callback();
}
}
}
var handler = fireIfElementVisible (shown[shown.length - 1], callback);
$(document).on('DOMContentLoaded load resize scroll', handler);
Above function will return boolean on whether your element is currently viewable on screen.
Test if the image I am about to grab using code is visible to the user or not
Limitations:
Plain javascript - please do not suggest jQuery or other framework
I am only interested in display:none and visibility:hidden but opacity and such is of course welcome
Code: below (taken from here) does not work in my DEMO
Question: Can you help making either work or suggest a better script?
Version A
function isVisible(obj){
if (obj == document) return true;
if (!obj) return false;
if (!obj.parentNode) return false;
if (obj.style) {
if (obj.style.display == 'none' || obj.style.visibility == 'hidden') return false;
}
else if (window.getComputedStyle) { // MY BAD - I PUT THE INCORRECT ELSE HERE
var style = window.getComputedStyle(obj, "");
if (style.display == 'none' || style.visibility == 'hidden') return false;
}
else if (obj.currentStyle) {
var style = obj.currentStyle;
if (style['display'] == 'none' || style['visibility'] == 'hidden') return false;
}
return isVisible(obj.parentNode);
}
Version B
function isVisible1(obj) {
var cnode = obj;
try {
while(cnode) {
if (cnode.nodeName) {
if (cnode.nodeName.toLowerCase()=="body") {
return true;
}
}
if (cnode.style.display=="none" || cnode.style.visibility=="hidden") {
return false;
}
cnode = cnode.parentNode;
}
return true;
}
catch(ex) {return false;}
}
Try taking the computed style conditionals outside the else of the style check. We want to check both the inline styles and the computed styles (from stylesheets.)
Changing:
else if (window.getComputedStyle) {
To:
if (window.getComputedStyle) {
Forked fiddle: http://jsfiddle.net/MXgbh/1/