Closed youngbrioche closed 5 years ago
If I understand correctly, add a class to you anchors and use
.anchor {
position:relative;
top:-100px;
}
That'd work for block elements but not for anchor links within text(paragraphs). Surely there're hacks like adding padding and negative margin at the same time, but wouldn't it be nicer if headroom could just unpin the header if someone jumps to an anchor that's above the current y position?
This would have been easy if @WickyNilliams would have chosen events, which give you the option to cancel them, over callbacks (see the discussion in #31). Although, I'm not really sure if callbacks are already implemented and if they are whether they allow for prevention of the pinning or not.
Could you explain how events would have helped here? I haven't pushed the callback code, so I could be swayed if there is a compelling use-case.
Well if you would fire an event for pinning and unpinning one could listen for a hashchange event and then preventDefault
the headroom event once.
I can't find any information on how I would catch the call to preventDefault
so that pinning or unpinning could be cancelled by a listener. Do you have any resources on this? Or could you provide a code sample?
One approach with callbacks might be to listen to hash change as you stated, and then call scrollTo(0,currentScrollY-heightOfHeader)
to adjust for the height of the header.
You can find a little demo here: http://jsbin.com/OkejIkEt/1/edit The essential part is the return value of EventTarget.dispatchEvent which will be false if the event has been canceled from within anywhere in the bubbling phase.
That makes sense, taught me something new :)
But callbacks don't preclude offering the same type of functionality. e.g. return false from callback if you wish to prevent pin/unpin
Yeah sure, I just feel that events seem more appropriate for a browser environment. Also, you can listen for an event as often as you wish whereas one can only register a single callback for pinning and unpinning, respectively.
I've pushed v0.4.0, and I decided to stick with callbacks approach. In the next version I'll allow pin/unpin to be cancelled by returning a flag, which will give @mrreynolds the ability to achieve what he wishes.
@hnrch02 I do agree, and I prefer the eventing paradigm in most cases but state is more neatly encapsulated with callbacks (it would complicate destruction of the widget if events were used).
Is this still in the works? Can't see how I can use the callbacks to achieve this.
Apologies, been super busy recently...
I'm not even sure if callbacks are needed. As I mentioned above, can listening to the hashchange event give you what you need?
This rough code seems to work on the headroom site where this problem is also exhibited e.g. jumping to a specific version in the changelog obscures some of the text. It hasn't been thoroughly tested
window.addEventListener("hashchange", function(e) {
var hash = location.hash.replace(/\./g, "\\."); //clean up hash
var anchor = document.querySelector(hash);
window.scrollTo(0, window.pageYOffset - anchor.offsetHeight);
}, false);
This adjusts the scroll for the height of the link, ensuring it's visible. Though it is simpler if we just adjust for the height of the header as we can cache everything up front:
var header = document.querySelector("header");
var headerHeight = header.offsetHeight;
window.addEventListener("hashchange", function(e) {
window.scrollTo(0, window.pageYOffset - headerHeight);
}, false);
~~Even if the problem of the TO might have been solved by the last comment, I wanted to ask, whether the feature mentioned in https://github.com/WickyNilliams/headroom.js/issues/38#issuecomment-32048463 that would "allow pin/unpin to be cancelled by returning a flag" has already been added? Can't find it in the docs, unfortunately.~~ (BTW, thanks a lot for the work on headroom!)
Update: In the meantime I solved my issue pretty roughly: I just added a further options class used to freeze the state of the header (pinned/unpinned). The code changes were minimal: adding a new options class and evaluate it in the shouldPin and shouldUnpin functions. What is then left to do is to add this class when the header should not pin/unpin.
Headroom.options = {
tolerance : 0,
offset: 0,
classes : {
pinned : 'headroom--pinned',
unpinned : 'headroom--unpinned',
top: 'headroom--top',
notTop: 'headroom--not-top',
initial : 'headroom',
isFrozen : 'frozen'
}
};
shouldUnpin : function (currentScrollY, toleranceExceeded) {
var scrollingDown = currentScrollY > this.lastKnownScrollY,
pastOffset = currentScrollY >= this.offset,
isFrozen = this.elem.classList.contains(this.classes.isFrozen);
return scrollingDown && pastOffset && toleranceExceeded && !isFrozen;
},
shouldPin : function (currentScrollY, toleranceExceeded) {
var scrollingUp = currentScrollY < this.lastKnownScrollY,
pastOffset = currentScrollY <= this.offset,
isFrozen = this.elem.classList.contains(this.classes.isFrozen);
return !isFrozen && ((scrollingUp && toleranceExceeded) || pastOffset);
},
For sure this is not a good solution, but in case somebody needs something similar and stumbles over this, it might be useful.
Thanks @acristinziani your amend works a treat, also thanks to @WickyNilliams for this excellent widget.
Any chance on getting @acristinziani's solution merged?
EDIT: or the one @WickyNilliams posted? Seems less complicated.
A simple but effective hack:
$(window).on('hashchange',function(){
setTimeout(function(){
headroom.unpin();
},0);
});
I am using setTimeout
to schedule the cancellation immediatly after the scroll
and headroom is triggered.
I would benefit from this as well. I tried @markmarijnissen's trick, but it didn't work.
edit: nevermind, I forgot to convert it to vanilla js... it's working now :p
While this doesn't solve the issue at hand (i.e. "anchor jumps to a higher part of the page"), I wanted to drop this here for the weary traveler.
The vanilla js version of @markmarijnissen's trick:
document.addEventListener('hashchange', function(){
setTimeout(function(){
headroom.unpin();
},0);
});
This seems to only work if you increase the timeout… I suspect a race condition with the scroll events & the debouncer (which uses requestAnimationFrame).
var header = document.querySelector("header");
var headerHeight = header.offsetHeight;
window.addEventListener("hashchange", function(e) {
window.scrollTo(0, window.pageYOffset - headerHeight);
}, false);
This solution works, but only when moving up the page, and only when clicking on "new" anchors. If you click on an anchor and are jumped/scrolled up the page, and then you scroll down and click the same anchor again, the hashchange
event is not fired. You would need to add some logic to support anchor links that scroll the page down and anchor links that are equal to the current hash.
To hide the header when loading a page with a hash use:
// Unpin header on URL fragments
if(window.location.hash) {
header.classList.add("headroom--unpinned");
}
As advised here, I would consider using the standardised scroll-padding css property. This has pretty good browser support (which will only improve over time), is built exactly for this purpose, and avoids awkward JS workarounds.
For that reason, I'm going to close. It only took 5 years to get to this point haha
I would consider using the standardised scroll-padding css property. This has pretty good browser support
Unfortunately, scroll-padding
doesn't work in Safari browser without some tricks. On my recent project, I've tried to make it work without success, and in the end I've had to resort to some custom plugin like vue-scrollto
.
Many thanks for the library, it's fantastic.
I'm struggling to get scroll-padding-top to work. It works if you have manually set it in the tag BEFORE headroom is loaded. But if you want to work out the height of the header first then set that to the scroll-padding-top in the browser ignores it. I'm guessing the browser needs to jump down to the right place in the page early on and doesn't get the updated value.
Anyone manage to get it working where it's dynamically setting the scroll-padding-top to the height of the header?
Thanks.
Is there any way to prevent pinning on anchor jumps to a higher part of the page, so anchors don't get obstructed by a pinned header?