Fixed header highlighted link behaviour not 100% correct - javascript

For a fixed header I add/remove an active class to the anchors like this:
// Store basic variables:
var win = $(window),
sec = $('section'),
nav = $('nav'),
anc = $('nav a'),
pos = nav.offset().top, // Distance of navigation to top of page
arr = sec.map(function(){return $(this).offset().top}).get(); // Distance of each section to top of page in an array
// Make function to add/remove classes:
win.scroll(function(){
var t = win.scrollTop(); // Distance of window top to top of page
t > pos ? nav.addClass('sticky') : nav.removeClass('sticky'), // Compare viewport top with top of navigation
// Compare each section position:
$.each(arr, function(i, val) {
(t >= Math.floor(val) && t < (val + sec.eq(i).outerHeight(true))) ? anc.eq(i-1).addClass('active')
: anc.eq(i-1).removeClass('active')
})
})
On some sections however at the very beginning of the section (i.e. after clicking the anchor and not scrolling further) the active class of the previous section (which is not in viewport anymore) won't get removed. Most probably due to calculations returning significant digits?
How can I get the calculations right so only the current section in viewport gets its anchor highlighted?

While I found some very weird behaviour during debugging this, it's as simple as substracting one pixel from the height of the section:
t >= val && t < (val + sec.eq(i).outerHeight(true) -1) ? ...
The rounding now happens directly inside var arr: return Math.floor($(this).offset().top)
What's really weird here is that even though I rounded everything down in the end the less than condition got returned true even though it mathematically wasn't...
For example this would return true (which apparently isn't):
1824 >= 912 && 1824 < (912 + 912)
So I had to substract 1px to make this true after all.
To me it seems as if jQuery's .outerHeight() prints no significant digits, but does include them. As in the documentation it says:
"The numbers returned by dimensions-related APIs, including .outerHeight(), may be fractional in some cases. Code should not assume it is an integer."
Which gets weird when rounding down doesn't work on it. In my fiddle I put the height of the sections to 912.453125px and .outerHeight() returned 912 but rounding it down with Math.floor() still seemed to return the fractions although it wouldn't print. (See this fiddle, where, when you go to section two and press the debug button, the calculation would be the above example)
So yeah, whatever. I'd like to have a more logical solution but substracting a pixel works.

Related

How do I check my condition on an if statement to see if it is working correctly?

I'm trying to make a type of circular display which cycles through a series of values as well as moving text elements within an svg file. It uses the hammer.js library and uses the drag event. The values can go in either direction. I have it working to some degree. As the last value is shown from an array, it goes back to the beginning of the array to get the first values. Or vice-versa.
var keyArray = ["C","C#","Db","D","D#","Eb","E","F","F#","Gb","G","G#","Ab","A","A#","Bb","B"];
This is my array. Here is how I wrap it past the end of the array and back to the beginning.
** As per the request of a few commenters and the suggested solution by Nina, I have modified the code below to reflect their suggestions. I have also added a few more lines for clarity of what is happening overall.**
var delta = keyArray.length - 5; // can be constant, it is always positive
for(i=0;i<5;i++){
//5 svg text element containing 5 musical keys
keys = document.getElementById("keys"+i);
//ev.deltaX is the change received from the hammer pan event
//text element moves relative to its original starting X
keys.setAttribute("x",startingSpots[i]+ev.deltaX%150);
currentX=keys.getAttribute("x");
currentEdgeIndex=keyArray.indexOf(keys.textContent);
//This if is what appears to be failing.
if (keys.getAttribute("x")>=565){
keys.setAttribute("x",currentX-150);
keys.textContent = keyArray[(currentEdgeIndex + delta) % keyArray.length];
}
}
With the suggested changes, I removed the Number() calls as well as implementing the modulus for the wrapper. The behavior is still erratic. On the example below, if you pan to the right, as the first text element reaches 565, it meets the condition for the if, is moved back to the left by 150.
What it should do next is to change the textContent to the next appropriate value in the array. However, this is where it becomes erratic, it is no longer past 565 so it does not meet the condition of the if statement, but the text changes at every increment of the pan event as if it were.
I am sure I am not seeing something simple that is causing the trouble but not sure what it is.
The array does appear to be circling correctly, though I'm still not sure "How can I check to see if the if statement is being correctly evaluated and met?"
The project can be viewed here. http://codepen.io/cmgdesignstudios/pen/zrmQaE?editors=1010
* Edit with solution *
Nina suggested the problem lie in the handling of the touch event. After further investigation, I found she was correct. I had originally been moving the object relative to its starting position and the deltaX from the touch event. I then was trying to change the current position by simply moving it back to the left rather than adjusting the starting position.
So I replaced the
keys.setAttribute("x",currentX-150);
with
startingSpots[i]-=150;
This kept the movement relative to the starting position and the deltaX of the touch event.
Please delete the Number(...) casting where it's not necessary. ...length returns always number and the result of calculation is a number too.
Your actual key feature is to move 5 entries down, and this can be achieved wit a simple addition and a modulo, to keep the value in a specific range, like
keys.textContent = keyArray[(keyArray.length + currentEdgeIndex - 5) % keyArray.length];
Further simplified can this calculation then lead to just add a positive number of
delta = keyArray.length - 5; // can be constant, it is always positive
keys.textContent = keyArray[(currentEdgeIndex + delta) % keyArray.length];
and make the modulo out of it.

How to detect an empty space on a web page

Is there a way to detect an empty area, without text or images within a web page, using JavaScript?
More precisely, how to determine whether point [x,y] is within a blank area, like in the following example (marked in red)
EDIT: I want to make my question clearer, I'm building an extension which supposed to mark search results as trustworthy or as spam, I want to put my marking at the end of the text of a result item URL.
I also want to do it in a generic way, so it wouldn't work only in Google web page. an example is shown below:
You can test for genuine white space like this :
function isWhiteSpace(coords) {
var element = document.elementFromPoint(coords.x, coords.y);
var whitespace = $(document).add("body, html");
return (whitespace.get().indexOf(element) > -1) ? true : false;
}
where coords is an object with .x and .y properties.
DEMO
document.elementFromPoint(), documented here, is "an experimental technology", so I wouldn't trust my life to it. Test thoroughly on all target platforms.
Edit
For the full detection of all the white you seek, isWhiteSpace() would be the first of two stages. The second stage would be isVisualWhiteSpace() implemented with #remdevtec's approach for example.
As my isWhiteSpace(coords) is inexpensive, you would perform it first and only if it returned false go for the expensive test. You could use the protective property of ||
var isWhite = isWhiteSpace(coords) || isVisualWhiteSpace(coords);
But your real problem will be writing isVisualWhiteSpace(). I can't help with that.
One approach would be to work with a screenshot of the window.
You can use libraries like html2canvas to load a screenshot to a HTML canvas element.
Next, on window.onclick, use the automatic event parameter to get an RGBA array of the clicked coordinate:
var pixelData = canvas.getContext('2d').getImageData(
event.offsetX,
event.offsetY, 1, 1)
.data;
Now if all (or at least the first three) pixelData's items equal 255, it means that this point is white.
if (pixelData[0] == 255 && pixelData[1] == 255 && pixelData[2] == 255) {
// the clicked point is white,
// and if the background-color is white,
// it can be considered an empty point
}
Of course, the down side is that you have to know the background color of the site you're testing, or the background color of the element you click in.
You can build a matrix with width and length of the page.
Set all matrix cells to zero.
Get all elements of the DOM.
Get x, y, width, and height of each element, this link may help
Retrieve the position (X,Y) of an HTML element
Draw the elements in the matrix
for(k=0;k < dom_elements.length;k++) {
for(i=dom_elements[k].y;i < dom_elements[k].length;i++) {
for(j=dom_elements[k].x;j < dom_elements[k].width;j++) {
matrix[i][j] = 1 ;
}
}
}
And finally check if matrix[i][j] is set to zero or 1

Scrollable DIV; check which div inside is scrolled to

I am working on a calender.
The structure is simple: The outer div #calender contains all the date-fields with class .field and ID as #DD-MM-YYYY. Now, I want the month name on top ("January") to February when the user scrolls past the #01-02-2015 DIV and so on... So the month name is dynamic.
Question:
HOW do I detect which div is scrolled to?
You can't really know which div is scrolled to. You can compare the original location of a div (relative to document), and see if the scrolled window has reached that location.
window.pageYOffset will give you the number of pixels the document has already scrolled to, whenever it is requested. Now what you'll need is to attach an event listener to the scroll event, give it the relative-to-document-top location of #01-02-2015 div, and checks to see if window.pageYOffset is greater than that. If it is, you can replace the month name. If it's less, you can replace it back.
To know the #01-02-2015 div's offset relative to document, you can jquery's offset() function, or try out the following and let me know if it works:
function getOffsetTop(element){
var offsetTop = 0;
do {
if ( !isNaN( element.offsetTop ) )
offsetTop += element.offsetTop;
} while( element = element.offsetParent );
return offsetTop;
}
(Adapted from finding element's position relative to the document )
** EDIT **
So, as you say the getOffsetTop function works. At page load, you'll want to get the locations/offsets of all the .first div's, and let's assume you also gave them a class or data-id with the specific month. So, let's start by creating an array of these values, like:
var firsts = document.querySelectorAll('.first');
var array = []
for(var i=0;i<firsts.length;i++)
array.push({month:firsts[i].getAttribute('data-id'), offset:getOffsetTop(firsts[i])});
Now you have an array that looks like [{month:'january',offset:214},{month:'february',offset:462}...].
Again, without looking at code, I'm thinking you will need to have a global (or anyway declared outside the scroll function) variable, declared at load time, that stores the index of the current month being looked at (assuming window.pageYOffset is zero then, you can start with array[0]). The scroll function will need to keep checking against the array item before and after this month to see if either of those scroll points are being reached, and if so, change the div content as you need, and also update the currentMonth variable. Something like:
var currentMonth = 0;
window.onscroll = function(){
var index = currentMonth;
if(array[index-1] && window.pageYOffset < array[index-1].offset)){
// change the specific div's innerHTML to that of previous one. You can also target a particular `first` by using document.querySelector('.first[data-id="'+array[index].month+"]'):
currentMonth = currentMonth - 1;
}
else if(array[index+1] && window.pageYOffset > array[index+1].offset)){
// change the div to next one
currentMonth = currentMonth + 1;
}
}
I haven't tested any of this but let me know how you make out, or if the console throws errors.
** EDIT3 **: MDN reference (link) recommends using window.pageYOffset instead of window.scrollY for cross-browser compatibility, so I've updated my answer.
I would calculate it on base of .field height and current scroll of #calendar.
Current scroll of div you can get with the following code:
var position = document.getElementById('calendar').scrollTop;
Assuming you already have your code in place that highlights the next date on scroll, you probably have a handle on the next date that will be highlighted. A simple approach would be on scroll, get the value of the highlighted div (should be the date). If the value === 1, it's the first of a new month so display the next month.
To get the next month, you could store an array of all months and then iterate over the array. If the current iteration is equal to the current month on the calendar, return the next element of the array
function getNextMonth(current_month) {
var month_array = [
'January',
'February',
'March',
'etc'
];
$.each(month_array, function(month, foo) {
if(month == current_month) {
return month_array[($.inArray(current_month, month_array) + 1) % month_array.length];
}
});
}
var calendar = $('calendar');
calendar.hover(function() {
calendar.scroll(function() {
//Your logic to change highlighted div
var current_date = $('#datediv').val(),//However you are selecting your current date div
month = $('#month_div');
if(current_date === 1) {
var next_month = getNextMonth(month);
month.val(next_month);
}
});
});
Honestly though, from a user standpoint it would be odd to scroll through dates on a calendar like that. With most calendars I've seen, scrolling will change the month. That may be a better design method for you unless you have to do it the way you're doing it now.
You could use .offsetTop to check the current y position of each cell, calculate which row is currently shown on top, and then check the current month through its id.
for (var i=0; i < rows.length; i++) {
// Select the last day, incase the first few days are still from the previous month
var lastcell = rows[i].querySelector(".field:last-of-type");
// Check if offsetTop is smaller than 100 (leave some error margin)
if (lastcell.offsetTop < 100) {
var id = lastcell.attribute("id").split("-");
var month = id[1];
// Update month
}
}
Add hidden divs as separators between each month (use css for this). Maybe each month should start on new (css) line. Then you can detect which month you are into by getting the scroll offset of the calendar and see between which separators is in. Assuming you know the offset of each separator or you can loop through with jQuery and the get the one that is closer.
UPDATE:
Between the dates of each month add a hidden separator div e.g <span class="month-separator"></span>. Then these separators by definition will have different offsets (since the dates of a whole month are between each one of them). Then when user scrolls, compute the current offset loop through each separator and see if the offset is close to one of them:
pseudo-code:
var calendarScroll = calendar.pageYOffset; // compute this
var whichMonth = 1; // start with january, i.e 1st month
// for each month separator
$('.month-separator').each(function(index){
// break when encountering the month that is not yet scrolled to
if ($(this).offset().top > calendarScroll)
{
whichMonth = index;
return false; // break from each loop
}
});
You will probably want to put the code above inside the scroll handler like the window.onscroll event handler (e.g see example with sticky header on scroll here).

Modify FireFox extension: If (scrollbar exists) var - 30 else var -14

Hello fellow code people :)
I am a frontend web developer and as such in need of constant knowledge of the actual viewport size in order to see where in responsive designing breakpoints start and end.
I know FF's own 'test window size' function, but came across a very handy extension: FireSizer.
the extension has one itsy bitsy drawback: It gives back the window-size including FF's borders and scrollbar. I need the viewport-size though. So I need the extension hacked, but dont't know enough javaScript to do so. Maybe someone is willing to help em out here?
I would love the extension to actually look for the scrollbar, and subtract from the width
a) 14 if no scrollbar present or
b) 30 if scrollbar present
I found of what I think is the right place to alter the code:
//
// Update the status bar panel with the current window size
//
function FiresizerUpdateStatus() {
var width = window.outerWidth + ''; // <- Think code needs to be edited here
var height = window.outerHeight + '';
document.getElementById("firesizer-statuspanel").label = width + 'x' + height;
}
Thanks for any effort!
AO
#Chen Asraf:
Well thank you very much. I didn't know there was an element to call the document-width. I changed the code to the following, and that did the trick (also when compared to FF's own 'Responsive Design View mode', which is spot on, its off by 2px - which i subtract from clientWidth.)
function FiresizerUpdateStatus() {
var width = window.outerWidth + ''; // changed this line to:
var width = document.documentElement.clientWidth-2 + '';
var height = window.outerHeight + '';
document.getElementById("firesizer-statuspanel").label = width + 'M' + height;
}
Thanks
AO
Possible duplicate of Get the browser viewport dimensions with JavaScript
Seems like you can get the window's inner dimensions by using:
// My window is maximized; screen is 1366x768
alert(document.documentElement.clientWidth);
// ^ returns 1349 (17 missing pixels because of scrollbar)
alert(document.documentElement.clientHeight);
// ^ returns 643 (125 pixels missing because of start bar & Chrome toolbars)
You can then compare the following with whatever else you need (for example, compare client width with window width to find if the difference is big enough to be a scrollbar - just experiment with the sizes)

How can I get the scrollbar position with JavaScript?

I'm trying to detect the position of the browser's scrollbar with JavaScript to decide where in the page the current view is.
My guess is that I have to detect where the thumb on the track is, and then the height of the thumb as a percentage of the total height of the track. Am I over-complicating it, or does JavaScript offer an easier solution than that? What would some code look like?
You can use element.scrollTop and element.scrollLeft to get the vertical and horizontal offset, respectively, that has been scrolled. element can be document.body if you care about the whole page. You can compare it to element.offsetHeight and element.offsetWidth (again, element may be the body) if you need percentages.
I did this for a <div> on Chrome.
element.scrollTop - is the pixels hidden in top due to the scroll. With no scroll its value is 0.
element.scrollHeight - is the pixels of the whole div.
element.clientHeight - is the pixels that you see in your browser.
var a = element.scrollTop;
will be the position.
var b = element.scrollHeight - element.clientHeight;
will be the maximum value for scrollTop.
var c = a / b;
will be the percent of scroll [from 0 to 1].
document.getScroll = function() {
if (window.pageYOffset != undefined) {
return [pageXOffset, pageYOffset];
} else {
var sx, sy, d = document,
r = d.documentElement,
b = d.body;
sx = r.scrollLeft || b.scrollLeft || 0;
sy = r.scrollTop || b.scrollTop || 0;
return [sx, sy];
}
}
returns an array with two integers- [scrollLeft, scrollTop]
It's like this :)
window.addEventListener("scroll", (event) => {
let scroll = this.scrollY;
console.log(scroll)
});
Answer for 2018:
The best way to do things like that is to use the Intersection Observer API.
The Intersection Observer API provides a way to asynchronously observe
changes in the intersection of a target element with an ancestor
element or with a top-level document's viewport.
Historically, detecting visibility of an element, or the relative
visibility of two elements in relation to each other, has been a
difficult task for which solutions have been unreliable and prone to
causing the browser and the sites the user is accessing to become
sluggish. Unfortunately, as the web has matured, the need for this
kind of information has grown. Intersection information is needed for
many reasons, such as:
Lazy-loading of images or other content as a page is scrolled.
Implementing "infinite scrolling" web sites, where more and more content is loaded and rendered as you scroll, so that the user doesn't
have to flip through pages.
Reporting of visibility of advertisements in order to calculate ad revenues.
Deciding whether or not to perform tasks or animation processes based on whether or not the user will see the result.
Implementing intersection detection in the past involved event
handlers and loops calling methods like
Element.getBoundingClientRect() to build up the needed information for
every element affected. Since all this code runs on the main thread,
even one of these can cause performance problems. When a site is
loaded with these tests, things can get downright ugly.
See the following code example:
var options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
var observer = new IntersectionObserver(callback, options);
var target = document.querySelector('#listItem');
observer.observe(target);
Most modern browsers support the IntersectionObserver, but you should use the polyfill for backward-compatibility.
If you care for the whole page, you can use this:
document.body.getBoundingClientRect().top
Snippets
The read-only scrollY property of the Window interface returns the
number of pixels that the document is currently scrolled vertically.
window.addEventListener('scroll', function(){console.log(this.scrollY)})
html{height:5000px}
Shorter version using anonymous arrow function (ES6) and avoiding the use of this
window.addEventListener('scroll', () => console.log(scrollY))
html{height:5000px}
Here is the other way to get the scroll position:
const getScrollPosition = (el = window) => ({
x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
});
If you are using jQuery there is a perfect function for you: .scrollTop()
doc here -> http://api.jquery.com/scrollTop/
note: you can use this function to retrieve OR set the position.
see also: http://api.jquery.com/?s=scroll
I think the following function can help to have scroll coordinate values:
const getScrollCoordinate = (el = window) => ({
x: el.pageXOffset || el.scrollLeft,
y: el.pageYOffset || el.scrollTop,
});
I got this idea from this answer with a little change.

Categories

Resources