In my Jquery Mobile website
I am using href for back button like;
<a id='{0}' class='{1}' href='/' data-role=""button"" data-icon=""arrow-l"" data-transition=""slide"" data-direction=""reverse"">
but if I have scroll on first page, back button jumps back to top again.
First page does not stay on same position.
Is there any solution for this?
Solution
I had this issue i fixed it using iSroll
While going from PageA to PageB save the scroll position of PageA in a variable.
to do this modify the iscroll.js and add getScrollY method under scrollTo like this
scrollTo : function(x, y, time, relative) {
var that = this, step = x, i, l;
that.stop();
if (!step.length)
step = [{
x : x,
y : y,
time : time,
relative : relative
}];
for ( i = 0, l = step.length; i < l; i++) {
if (step[i].relative) {
step[i].x = that.x - step[i].x;
step[i].y = that.y - step[i].y;
}
that.steps.push({
x : step[i].x,
y : step[i].y,
time : step[i].time || 0
});
}
that._startAni();
},
getScrollY : function() {
var that = this;
return that.y;
},
Now save the current position before page change like this
curScrollPos = myScroll.getScrollY();
And set the scroll position while going back to that PageA, i am doing this on pagehide event of PageB
myScroll.scrollTo(0, curScrollPos, 1);
myScroll.refresh();
This way i solved my issue, hope this helps.
More info
If you want to find out more about this topic take a look at this article, you will also find working examples.
why don't you directly add data-rel="back" on the anchor tag and set href="#" instead ?
<a id='{0}' class='{1}' href='#' data-rel="back" data-role="button" data-icon="arrow-l" data-transition="slide" data-direction="reverse"/>
Before I describe your available solutions you need to understand, this is not an error nor is there a perfect solution. The issue is that to animate the transition to another page the viewport has to be at the top of the page so that the current page and the page transitioning in are vertically lined-up.
If you were half-way down a long list on one page (say 1000px) and the page you are transferring to is only a few hundred pixels tall then the current page would animate off the screen properly but the new page would not be visible as it would be above the viewport.
There are 2 viable solutions:
iScroll and its jQuery Mobile derivate iScrollview
iScroll homepage: http://cubiq.org/iscroll-4
iScrollview homepage: https://github.com/watusi/jquery-mobile-iscrollview
iScroll is a javascript that can scroll content in a window within a web browser with very similar behaviour to native scrolling on mobile devices such as iPhone and Android. This means you can scroll a window within the browser using native-like scrollbars and physics.
That is also a solution for our current problem. Because of iScroll implementation pages will occupy 100% of page height, no matter how far listview is scrolled. This is also a reason why on return listview will still stay at a same position.
Of course in case you want to implement this solution you should pick iScrollview implementation. You would still be able to implement iScroll, but it would take you much more time.
Silent scroll
Official documentation: http://jquerymobile.com/demos/1.1.0-rc.1/docs/api/methods.html
This jQuery Mobile functionality is also the same reason why we have this problem at the first place. Before a page transition is triggered original page is silently scrolled to the top.
In our case, when listview is selected, its position must be remembered (here you will find solutions of data/parameteres storing during the page transition, just search for the chapter: Data/Parameters manipulation between page transitions) before page is changes. In that case, when we return to the previous page we could use pagebefpreshow event to silently scroll to the bottom before page is shown.
//scroll to Y 100px
$.mobile.silentScroll(100);
And here's a working example of silent scroll: http://jsfiddle.net/Gajotres/2xfrM/
And here's a real life jsFiddle example using large listview and several pages: http://jsfiddle.net/Gajotres/5zZzz/
// Detect click on a li element and store its coordinate, change page to another
$(document).on('pagebeforeshow', '#index', function(){
$('#test-list li').on('click', function(){
storePosition.topCoordinate = $(this).offset().top;
$.mobile.changePage('#second');
});
});
// If there's a stored position use silentscroll function and scroll to correct location
$(document).on('pageshow', '#index', function(){
if(storePosition.topCoordinate !== null) {
$.mobile.silentScroll(storePosition.topCoordinate);
}
});
// Store position location
var storePosition = {
topCoordinate : null
}
Unfortunately like in your example, this solution works only on pageshow. Because of jQM architecture it is only possible to do this during the pageshow event.
Final notes
If you want to find out more about iScroll + iScrollView, how they work with working examples then take a look at this article.
I found a solution here: https://forum.jquery.com/topic/jquery-mobile-scroll-to-top-of-page-on-page-load#14737000005271291
(function($){
$( document ).on( "mobileinit", function() {
var silentScroll = $.mobile.silentScroll;
$.mobile.silentScroll = function( ypos ) {
if ( $.type( ypos ) !== "number" ) {
// FIX : prevent auto scroll to top after page load
return;
} else {
silentScroll.apply(this, arguments);
}
}
})
}(jQuery));
Related
I am trying to make some collapsible accordion containers on my website accessible, but I am running into an issue.
The accordions are controlled by link elements on the page - this way, a keyboard-only user can tab to them and access them. The first issue I ran into was that if a user tabbed to one of the links, the page wouldn't always scroll up to show them which one they had tabbed to. I fixed this issue setting the focus using the following code, which scrolls the link to the top of the viewport:
$(".accordion .accordion-item .accordion-heading a").focus(
function()
{
$('html:not(:animated), body:not(:animated)').animate({
scrollTop: $(this).offset().top
}, 250);
}
);
The problem I am encountering now is that when a mouse-user clicks on the link, it jumps to the top of the page and does not open the container unless the mouse-user clicks the link again.
Is there a way I can set the focus code above to only fire if the link has been tabbed to? Or, is there a better way of handling the focus issue so that it works for both keyboard-only and mouse users?
Thanks!
Firstly a quick apology, having now seen your accordion is built correctly, links with in-page anchors are actually preferable if the accordion is constructed using javascript on page load and falls back to just a list of in page anchor links and content between them.
I am that used to seeing <a href="#"> on accordion openers and weird accordion implementations I jumped to conclusions, change it back from <buttons>!
Fixing your problem
Probably not the answer you are looking for but remove the .focus() function entirely.
It produces strange behaviour where if I have one accordion item open and i tab back with Alt + Tab quickly scrolling can be really confusing as it jumps around if you tab quicker than the scroll.
One of the golden rules of accessibility is to only adjusted the scroll position on a page if it is expected (i.e. a return to top button or using in-page anchors).
In the example and on your website once I disabled the 'scroll to top on focus' the site actually behaved as expected.
I understand why you did it as occasionally a link that is focused appears off the page, however this remedies itself when you tab again or by scrolling down (your site is logical so that if I tab and my focus is not visible I know it is off the page.)
This tends to happen (items not scrolling into view) when the item is just out of sight, by a px or two, it is common and ironically now falls into 'expected' behaviour (another rule, follow accepted and expected behaviour when designing components and pages).
If you really want to fix it
In your focus function instead of just scrolling to the top of the page whenever an item is focused, check if it is off the page.
Below is an example function I found (not tested) that you can use to check if the item is in the viewport, if it is then don't do anything, if it isn't then do your scroll function.
var isInViewport = function (elem) {
var bounding = elem.getBoundingClientRect();
return (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
So roughly (yet again haven't tested that the correct items are passed in, this is just to give you an idea).
$(".accordion .accordion-item .accordion-heading a").focus(
function()
{
if(isInViewport(this) === false){
//item is not in the viewport so scroll it into view
$('html:not(:animated), body:not(:animated)').animate({
scrollTop: $(this).offset().top //I would perhaps add a couple of hundred pixels here to make the item appear in a more natural area.
}, 250); //remove the animation as a further accessibility improvement, animations can be off putting to people with motion or anxiety disorders.
}
}
);
This fixes your problem as no mouse user will ever be able to click an item that is off the page so they won't ever trigger the scroll event that causes the focus issue.
You can change the event setting: Instead focus() event you will do a click() event: When you click a link, you'll scroll up. This will solve the problem of both keyboard navigation and mouse clicking;And this is also more true in terms of accessibility.
$(".accordion .accordion-item .accordion-heading a").click(
function(e)
{
e.preventDefault();
$('html:not(:animated), body:not(:animated)').animate({
scrollTop: $(this).offset().top
}, 250);
}
);
Don't forget to change the link setting to a button by adding role=button attribute.
and add aria-expanded attribute.
I have a fixed header with :target in-page anchors, and need to adjust the property values dynamically via JavaScript or JQuery so as to maintain the relevant :target's position directly under the header when the window is resized, while adapting to the changes in both the previous section's .container height and the .header_container height that occur with resizing.
The simplest solution seems to be a ::before pseudo-element for the :target pseudo-class, and to then utilize CSS custom properties to dynamically modify the style properties.
I have no trouble correctly positioning the :target with the below function when the page is loaded (or reloaded), or correctly position the first :target on $(window).resize(), however it's failing to do the same for the remaining targets on $(window).resize().
Fiddles
Simplified Code: https://jsfiddle.net/chayanyc/g6p3549s/
Responsive Design (Simplified): https://jsfiddle.net/chayanyc/wuk92dns/
Code Snippets
CSS:
.header_container {height: 98px; margin: 0; padding: 0; position: fixed; top: 0; display: block; z-index: 100;}
.main {margin-top: 98px; width: 100%;}
:target::before {height: var(--target_position1); margin-top: var(--target_position2); content: ""; display: block; visibility: hidden;}
JavaScript:
var headerHeight;
function setTarget() {
headerHeight = document.getElementById('header').offsetHeight;
headerHeight1 = headerHeight + "px";
document.documentElement.style.setProperty('--target_position1', headerHeight1);
document.documentElement.style.setProperty('--target_position2', '-' + headerHeight1);
}
$(window).resize(function () {
setTarget();
});
$(document).ready(function () {
setTarget();
});
There is no complete solution to this Problem,
because you want the target element stay on place on document resize, but if the user do a scroll on his page, it is not possible to know where staying on the same first word of the first line on display.
So here, i just replace on the same target on top when user resize his document, even if he had done a scroll just before.
no need of this CSS part (remove it)
:target::before {margin: 0; content: ""; dis.....
and change your jQuery to:
$(document).ready(function () {
// global info for menu -> target elememt
var InfoTarget = { ID: null, tempo:300 }
$('a').click(function(evt){
InfoTarget.ID = $(this).attr('href') // possible target elm
// check if InfoTarget.ID exist on page
let nbElements = 0
try { nbElements = $(InfoTarget.ID).length }
catch(err) { nbElements = 0 }
if ( nbElements != 1 ) {
InfoTarget.ID = null // not target element found
}
else {
evt.preventDefault() // disable auto scroll to target element
$('html').animate({
scrollTop: ($(InfoTarget.ID).offset().top - $('#header').outerHeight(true))
}, InfoTarget.tempo );
}
});
$(window).resize(function (){
if (InfoTarget.ID) { // if InfoTarget.ID exist <=> InfoTarget.ID != null
$('html').animate({
scrollTop: ($(InfoTarget.ID).offset().top - $('#header').outerHeight(true))
}, InfoTarget.tempo);
}
});
});
My code speaks for itself, but here is a complete explanation:
the principle is simple: the function target css activates on a click on a link <a href="#..."> to trigger a scroll of the page towards the element having for id = to that contained in the initial href.
therefore this code intercepts any click on a link on the page and must first determine whether it is a link to an anchor or not.
To determine if this is a link to an anchor on the page, it simply tests whether an element of the page has this value as this ID, (// check if InfoTarget.ID exists on page).
As this kind of test can also generate an error, this test is placed in a try / catch.
If the result is indeed an anchor, then the action of the click is canceled, (with evt.preventDefault()) which prevents the browser from triggering its automatic scroll to the link;
the reference link is kept in an object variable (global)
var InfoTarget = {ID: null, tempo: 300}
seen on: InfoTarget.ID = $(this).attr('href') // possible target elm
the rest is simple, you have to scroll down to the anchor.
Depending on the width of the page and the previous elements, browsers continuously recalculate the position of each tag present on a page and jQuery can be retrieved this offset position by $(element).offset().Top
as there is a menu bar on your page that masks the top of the page, you must deduct its height from the position in scroll (= $ ('# header'). outerHeight (true))
a scroll = 0 will force a move to the top of the page
a scroll = $(element).offset().top places the element at the top of the page
to which we must deduct the height of the #header
the complete formula is
scrollTop: ( $(InfoTarget.ID).offset().top - $('#header').outerHeight(true) )
this command is placed in a jQuery.animate, for a visually smoother move, and uses the InfoTarget.tempo value as the duration for this animation.
During a resize of the page, and to the extent that a link having a target has been previously clicked (therefore always active) then the same type of scroll is triggered.
The different jQuery methods used are all explained in the jQuery doc (for example: https://api.jquery.com/outerHeight/ )
New Solution -- Lundi 14 oct 2019 / 01:00 (in the night of sunday / monday)
this script must be placed after all the html elements of the body
// scroll to target upon window.location.hash
$(window).on('hashchange', function() {
$('.TargetMark').removeClass('TargetMark')
$(window.location.hash).addClass('TargetMark')
setTimeout( scrollTop2, 220 ) // scroll to target after browser auto scrolling conflit
})
function scrollTop2() {
if ($('.TargetMark').length===1) { // if target exist
$('html').animate({
scrollTop: ($('.TargetMark').offset().top - $('#header').outerHeight(true))
}, 100);
}
}
In this version the target element is added a class (TargetMark) allowing to find it when window resize
ending part
$(document).ready(function () {
//...
// ---------------------------> no call to scrollTop();
//...
});
$(window).resize(function () {
//...
scrollTop2();
//...
});
about toggleMenu conflict:
function toggleMenu() {
$('.navbar-toggle').on('click', function () {
if ($("#js-menu").is(".expand")) {
$("#js-menu").toggleClass("expand");
$("#submenu").removeClass("active_sub").addClass("inactive_sub");
} else {
$("#js-menu").toggleClass("expand");
$("#submenu").removeClass("inactive_sub").addClass("active_sub");
}
resetTarget();
setTimeout( scrollTop2, 220 ) // scroll to target after browser auto scrolling conflit
});
}
I spent a lot of my time on your question, I studied differents approaches and the different automatisms put at work by the navigators themselves and which is necessary to fight to get the result you'r looking for. I came to the conclusion that the problem first came from the architecture of your page.
The fact that the menu ("#header") covers the page ("#main") is a major flaw that prevents to have an effective JS code for your question.
The call on the hash of an anchor triggers one or more scrolls of the page, the resize of the page also entails a scroll calculation because by changing size on the screen, it also changes the size of the page. page (reducing the size of the screen by half makes the page size double), it is the same by changing the size of the font, it also changes the size in page.
Whenever the page size changes, the browser must recalculate a lot of things and some of these mechanisms can trigger one or more scrolls.
What you are asking here is to recalculate a page positioning according to an element of which we can not be certain that it is completely established because this process is executed in parallel with other processes of the browser which can change useful values.
Plus the fact that some of the browser processes also work to scroll the page and that it can be the last done!
So the fact that there is an overlap between the menu and the page add more complexity and makes the possibility of a solution impossible.
Change your layout and 3/4 of your problem will be fixed.
Resize is firing, offset height is not changing. Setting the same value over and over again, yields no change. You might check this:
see the value change
I used the logo for output:
$('.logo').text(headerHeight + ' -' + i++);
You want to scroll down to the one div selected by target without having it to be overlapped by your nav?
.. then extend the areas. see here
add positive margin-top and negative padding-top.
.... to compensate for any nav size changes, use media queries to change your css vars.
I would like to have a widget on a webpage containing a number of tabs. When the user scrolls the page and the widget comes in to view and he keeps scrolling down, the tabs should be activated one by one (without the page scrolling further down). Once the last tab is showing, the page should resume scrolling as usual. Is this doable using JS/jQuery?
UPDATE:
Since this seems too broad a question:
The problem is, I don't know how to use the scroll offset and prevent the page from scrolling down until I decide it can resume its normal behavior
UPDATE 2
I created This fiddle,
$(document).ready(function(){
$('#tabbed').mouseover(function(){
$(this).focus();
}).scroll(function(){
console.log("scrolling tabs");
});
$(window).scroll(function(evt){
var scrollPos = $(this).scrollTop()
console.log(scrollPos);
// BULLETPROOF WAY TO DETECT IF THE MOUSE IS OVER THE
// SCROLLABLE DIV AND GIVE IT FOCUS HERE?
});
});
it contains a long page and a scrollable div among its contents. The only problem is that the div starts catching scroll events only if I move my mouse. If I could find a bulletproof way to activate the scrolling div whenever the mouse is over it I'm there. Any ideas?
You can't prevent scrolling with javascript. Using iframes and divs with scroll will only work if the mouse is over them.
You can cancel the mouse wheel and keys events related to the scrolling, however the user will be able to scroll using the scrollbar (more here).
Another approach is leaving an empty area and fixing your widget inside this area, like in this working example
$(window).bind('scroll', function()
{
var scroll = $(window).scrollTop(),
innerHeight = window.innerHeight || $(window).height(),
fooScroll = $('#fooScroll'),
emptyArea = $('#emptyArea'),
offset = emptyArea.offset(),
fixedClass = 'fixed';
if(scroll > offset.top)
{
if(scroll < offset.top + emptyArea.height() - fooScroll.height())
{
fooScroll.addClass(fixedClass);
fooScroll.css("top", 0);
}
else
{
fooScroll.removeClass(fixedClass);
fooScroll.css("top", emptyArea.height() - fooScroll.height());
}
}
else
{
fooScroll.removeClass(fixedClass);
fooScroll.css("top", 0);
}
});
Then you can change the tabs while the page is scrolling.
You should be able to do this. You can use the jQuery scroll event to run your own code whenever the user scrolls up or down. Also, so long as you call e.preventDefault() whenever the scroll event is fired, you can prevent the whole window from scrolling up or down.
This question already has answers here:
Prevent scrolling of parent element when inner element scroll position reaches top/bottom?
(32 answers)
Closed 9 years ago.
I have a div that is scrollable, but whenever you reach the bottom/top of it, it begins to scroll the entire page. That could be annoying for users who scroll fast, and then the entire page starts scrolling unexpectedly.
I need something where if you are hovering over the div, the page is not scrollable.
I have tried this by adding CSS when I hover the div...
body {
overflow:hidden;
}
...It works but there is one problem. The scrollbar disappears and that looks kind of stupid to have it disappearing/reappearing. Any way to achieve the same effect but keep the scrollbar visible? I have seen it done with Facebook chat.
Here is a very simple way to stop the propagation with no plugins, just jQuery.
Update: The code has been updated to work correctly in IE9+. Have not tested in previous versions.
First, create a class on your <div> to mark it as having this behavior. In my example, I use the class .Scrollable.
<div class="Scrollable">
<!-- A bunch of HTML here which will create scrolling -->
</div>
The jQuery to disable is:
$('.Scrollable').on('DOMMouseScroll mousewheel', function(ev) {
var $this = $(this),
scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
height = $this.height(),
delta = (ev.type == 'DOMMouseScroll' ?
ev.originalEvent.detail * -40 :
ev.originalEvent.wheelDelta),
up = delta > 0;
var prevent = function() {
ev.stopPropagation();
ev.preventDefault();
ev.returnValue = false;
return false;
}
if (!up && -delta > scrollHeight - height - scrollTop) {
// Scrolling down, but this will take us past the bottom.
$this.scrollTop(scrollHeight);
return prevent();
} else if (up && delta > scrollTop) {
// Scrolling up, but this will take us past the top.
$this.scrollTop(0);
return prevent();
}
});
In essence, what this does is to detect which direction the scrolling is being requested in (based on the originalEvent.wheelDelta: positive = up, negative = down). If the requested delta of the mousewheel event would move scrolling past the top or bottom of the <div>, cancel the event.
In IE, especially, scrolling events which go past a child element's scrollable area then roll up to parent elements, and the scrolling continues regardless of the event being canceled. Because we cancel the event in any case, and then control the scrolling on the child through jQuery, this is prevented.
This is loosely based on the way that this question solves the problem, but does not require the plugin, and is cross-browser compliant with IE9+.
Here is a working jsFiddle demonstrating the code in-action.
Here is a working jsFiddle demonstrating the code in-action, and updated to work with IE.
Here is a working jsFiddle demonstrating the code in-action, and updated to work with IE and FireFox. See this post for more details about the necessity of the changes.
maybe have a look to
How to disable scrolling temporarily?
This is a sample to stop and activate scroll
I do not know how to solve this situation:
I`ve got the html/css looks like this:
Image showing how my css/html looks like and what is displayed on the screen after landing on page:
The when I scroll down I see green element:
scrolling down ->
After continuing to scrolling down I saw full green element and the if I scroll down I want to have this element like in css language: position fixed bottom 0. See image below:
I ve saw full element -> same link but image called problem3.png
and then I scroll below and I want to have it fixed at the bottom of the page, like on this image:
Fixed element on screen - What I want and I do not know how to do that -> same link but image called problem4.png (stupid spam prevention mechanism)
Is it possible to solve this situation ?
To sum up: I`ve got two divs, one above and second below, Wheen I scroll down I suddenly see another element (green div) and when i continue to scroll down I WANT TO HAVE THIS GREEN DIV FIXED AT THE BOTTOM OF THE PAGE.
Ofcourse, when I scroll up (back on the top) I want to "park" that green div at the top of the second div.
Is there any way to solve this situation with jQuery (Javascript) / html / css ?
Thank you in advance
I think you'll have to show some of your html structure. There are lots of ways to achieve this kind of effect. Fundamentally, in javascript terms you'll be looking to:
Add an event listener to the window scroll that checks whether the green element is fully in view
If it is in view, add a class (or change it's css) that fixes it's position where you want
Change your window scroll method so that it's checking the relative offset of the red div to the top of the screen. If it goes below the position where the green div should be fixed, remove the class you added earlier.
That sounds complicated, but it's not too bad. The javascript would be something like:
$(function() {
$(window).scroll(function() {
if($(".divToFix").hasClass("fixedAtBase")){
if(Utils.underView($(".redDiv"), $(".divToFix").height())) $(".divToFix").removeClass("fixedAtBase");
} else {
if(Utils.inView($(".divToFix"))) $(".divToFix").addClass("fixedAtBase");
}
});
});
Utils = {
underView: function(element, offset) {
return (($(window).height() + $(window).scrollTop() - offset) <= element.offset().top);
},
aboveView: function(element) {
return ($(window).scrollTop() >= element.offset().top + element.height());
},
inView: function(element) {
return (Utils.aboveView(element) !== true && Utils.underView(element, element.height()) !== true);
}
};
Bear in mind I've not tested that or anything.
edit - here's a demo