Animate scrollTop не работает в Firefox


164

Эта функция работает отлично. Прокручивает тело до желаемого смещения контейнера

function scrolear(destino){
    var stop = $(destino).offset().top;
    var delay = 1000;
    $('body').animate({scrollTop: stop}, delay);
    return false;
}

Но не в Firefox. Зачем?

-РЕДАКТИРОВАТЬ-

Для обработки двойного триггера в принятом ответе я предлагаю остановить элемент перед анимацией:

$('body,html').stop(true,true).animate({scrollTop: stop}, delay);

2
Ваш окончательный код является самым элегантным из всех вещей здесь.
Ящерица

Ответы:


331

Firefox размещает переполнение на htmlуровне, если он не предназначен для поведения по-другому.

Чтобы заставить его работать в Firefox, используйте

$('body,html').animate( ... );

Рабочий пример

Решением CSS было бы установить следующие стили:

html { overflow: hidden; height: 100%; }
body { overflow: auto; height: 100%; }

Я предполагаю, что решение JS будет наименее инвазивным.


Обновить

Большая часть обсуждения ниже фокусируется на том факте, что анимация scrollTopдвух элементов вызовет обратный вызов дважды. Функции обнаружения браузеров были предложены и впоследствии устарели, а некоторые, возможно, довольно надуманы.

Если обратный вызов идемпотентен и не требует больших вычислительных мощностей, его повторный запуск может быть полной проблемой. Если множественные вызовы обратного вызова действительно являются проблемой, и если вы хотите избежать обнаружения функции, может быть более простым обеспечить принудительное выполнение обратного вызова только один раз из обратного вызова:

function runOnce(fn) { 
    var count = 0; 
    return function() { 
        if(++count == 1)
            fn.apply(this, arguments);
    };
};

$('body, html').animate({ scrollTop: stop }, delay, runOnce(function() {
   console.log('scroll complete');
}));

51
Решение JS имеет недостаток! Функция animate выполняется дважды, соответственно, для элемента html и для элемента body . Похоже, что браузеры Webkit используют body, а остальные используют HTML . Это исправление должно сделать работу$(jQuery.browser.webkit ? "body": "html").animate(...);
Andreyco

2
Я обнаружил, что решение от Andreyco не работает в jQuery <1.4, который я использовал. Я был в состоянии исправить это следующим образом : $(jQuery.browser.mozilla ? "html" : "body").animate(...);. Было бы замечательно не использовать поиск по браузеру, а обнаружение функций. Не уверен, как сделать обнаружение функций в CSS
Руставоре

23
Обратите внимание, jQuery.browserчто устарела и отсутствует в последней версии jQuery
spiderplant0

2
Прошло 4 года, и этот ответ все еще оказывается полезным. Огромное спасибо!
Мэтт

1
@DamFa: Главным образом, потому window.scrollчто не оживляет. Вы, конечно, могли бы бросить свою вещь с интервалами и scrollBy. Если вы хотите добавить ослабление к этому, у вас неожиданно появляется много кода для довольно незначительного аспекта вашего продукта.
Дэвид Хедлунд

19

Было бы неплохо обнаружить объекты, а затем анимировать их на одном поддерживаемом объекте, но не существует однострочного решения. В то же время, вот способ использовать обещание сделать один обратный вызов за выполнение.

$('html, body')
    .animate({ scrollTop: 100 })
    .promise()
    .then(function(){
        // callback code here
    })
});

ОБНОВЛЕНИЕ: вот, как вы могли бы вместо этого использовать функцию обнаружения. Этот кусок кода должен быть оценен перед вызовом анимации:

// Note that the DOM needs to be loaded first, 
// or else document.body will be undefined
function getScrollTopElement() {

    // if missing doctype (quirks mode) then will always use 'body'
    if ( document.compatMode !== 'CSS1Compat' ) return 'body';

    // if there's a doctype (and your page should)
    // most browsers will support the scrollTop property on EITHER html OR body
    // we'll have to do a quick test to detect which one...

    var html = document.documentElement;
    var body = document.body;

    // get our starting position. 
    // pageYOffset works for all browsers except IE8 and below
    var startingY = window.pageYOffset || body.scrollTop || html.scrollTop;

    // scroll the window down by 1px (scrollTo works in all browsers)
    var newY = startingY + 1;
    window.scrollTo(0, newY);

    // And check which property changed
    // FF and IE use only html. Safari uses only body.
    // Chrome has values for both, but says 
    // body.scrollTop is deprecated when in Strict mode.,
    // so let's check for html first.
    var element = ( html.scrollTop === newY ) ? 'html' : 'body';

    // now reset back to the starting position
    window.scrollTo(0, startingY);

    return element;
}

// store the element selector name in a global var -
// we'll use this as the selector for our page scrolling animation.
scrollTopElement = getScrollTopElement();

Теперь используйте переменную, которую мы только что определили как селектор для анимации прокрутки страницы, и используйте обычный синтаксис:

$(scrollTopElement).animate({ scrollTop: 100 }, 500, function() {
    // normal callback
});

Отлично работает в нормальных условиях, спасибо! Однако есть пара моментов, о которых нужно знать. (1) Если в теле не хватает содержимого для переполнения окна при выполнении теста возможностей, окно не будет прокручиваться, и тест выдаст фиктивный результат «тело». (2) Если тест выполняется внутри iframe в iOS, он не проходит по той же причине. Фреймы в iOS расширяются по вертикали (а не по горизонтали), пока содержимое не уместится внутри без прокрутки (3) Поскольку тест выполняется в главном окне, он запускает обработчики прокрутки, которые уже установлены. ... (продолжение)
hashchange

... По этим причинам пуленепробиваемый тест должен выполняться внутри выделенного iframe (solves (3)) с достаточно большим содержимым (solves (1)), который проверяется на горизонтальную прокрутку (solves (2)).
hashchange

На самом деле мне пришлось запустить эту функцию в сценарии тайм-аута, чтобы это давало согласованные результаты, в противном случае она иногда возвращалась, htmlа иногда возвращалась body. Вот мой код после объявления функции var scrollTopElement; setTimeout( function() { scrollTopElement = getScrollTopElement(); console.log(scrollTopElement); }, 1000); Спасибо, хотя, это решило мою проблему.
Тони М

Не берите в голову, я понял, что является фактической проблемой. Если вы уже находитесь в нижней части страницы после перезагрузки (f5), этот скрипт вернется htmlвместо того, bodyкак предполагалось. Это только если вы прокручиваете всю страницу вниз и перезагружаете, иначе это работает.
Тони М

Это сработало для меня после того, как я добавил проверку, открывается ли страница внизу.
Скотт

6

Я потратил целую вечность, пытаясь понять, почему мой код не работает -

$('body,html').animate({scrollTop: 50}, 500);

Проблема была в моем CSS -

body { height: 100%};

autoВместо этого я установил его (и меня беспокоило, почему он был установлен 100%в первую очередь). Это исправило это для меня.


2

Вы можете избежать проблемы с помощью плагина - точнее, моего плагина :)

Серьезно, даже несмотря на то, что основная проблема уже давно решена (разные браузеры используют разные элементы для прокрутки окон), есть довольно много нетривиальных проблем, которые могут сбить вас с толку:

Я явно предвзят, но jQuery.scrollable самом деле - хороший выбор для решения этих проблем. (На самом деле, я не знаю ни одного другого плагина, который обрабатывает их все.)

Кроме того, вы можете вычислить целевую позицию - ту, к которой вы прокручиваете - в пуленепробиваемом виде с помощью getScrollTargetPosition()функции в этом списке. .

Все это оставит вас с

function scrolear ( destino ) {
    var $window = $( window ),
        targetPosition = getScrollTargetPosition ( $( destino ), $window );

    $window.scrollTo( targetPosition, { duration: 1000 } );

    return false;
}

1

Остерегайтесь этого. У меня была такая же проблема, ни Firefox, ни Explorer с прокруткой

$('body').animate({scrollTop:pos_},1500,function(){do X});

Так что я использовал, как сказал Дэвид

$('body, html').animate({scrollTop:pos_},1500,function(){do X});

Отлично, это сработало, но новая проблема, поскольку есть два элемента, body и html, функция выполняется дважды, то есть, X выполняется два раза.

пробовал только с 'html', и Firefox и Explorer работают, но теперь Chrome не поддерживает это.

Так что нужно тело для Chrome, и HTML для Firefox и Explorer. Это ошибка JQuery? не знаю

Просто остерегайтесь вашей функции, так как она будет выполняться дважды.


ты нашел обходной путь для этого? А поскольку обнаружение в браузере jquery устарело, я не знаю, как использовать обнаружение функций для различения chrome и firefox. Их документация не определяет функцию, которая присутствует в Chrome, а не в Firefox или наоборот. :(
Мэндип Джайн

2
как насчет .stop (правда, правда) .animate?
Тони Мишель Каубе

0

Я бы порекомендовал не полагаться bodyни htmlна более портативное решение. Просто добавьте в тело элемент div, предназначенный для хранения прокручиваемых элементов, и стилизуйте его так, чтобы включить полноразмерную прокрутку:

#my-scroll {
  position: absolute;
  width: 100%;
  height: 100%;
  overflow: auto;
}

(при условии , что display:block;и top:0;left:0;являются по умолчанию , который соответствует вашей цели), а затем использовать $('#my-scroll')для анимации.


0

Это реальная сделка. Работает на Chrome и Firefox без нареканий. Это даже грустно, что некоторые невежественные голосуют против меня. Этот код буквально работает отлично, как и во всех браузерах. Вам нужно только добавить ссылку и указать идентификатор элемента, который вы хотите прокрутить, в href, и это работает без указания чего-либо. Чистый многоразовый и надежный код.

$(document).ready(function() {
  function filterPath(string) {
    return string
    .replace(/^\//,'')
    .replace(/(index|default).[a-zA-Z]{3,4}$/,'')
    .replace(/\/$/,'');
  }
  var locationPath = filterPath(location.pathname);
  var scrollElem = scrollableElement('html', 'body');

  $('a[href*=#]').each(function() {
    var thisPath = filterPath(this.pathname) || locationPath;
    if (locationPath == thisPath
    && (location.hostname == this.hostname || !this.hostname)
    && this.hash.replace(/#/,'') ) {
      var $target = $(this.hash), target = this.hash;
      if (target) {
        var targetOffset = $target.offset().top;
        $(this).click(function(event) {
          event.preventDefault();
          $(scrollElem).animate({scrollTop: targetOffset}, 400, function() {
            location.hash = target;
          });
        });
      }
    }
  });

  // use the first element that is "scrollable"
  function scrollableElement(els) {
    for (var i = 0, argLength = arguments.length; i <argLength; i++) {
      var el = arguments[i],
          $scrollElement = $(el);
      if ($scrollElement.scrollTop()> 0) {
        return el;
      } else {
        $scrollElement.scrollTop(1);
        var isScrollable = $scrollElement.scrollTop()> 0;
        $scrollElement.scrollTop(0);
        if (isScrollable) {
          return el;
        }
      }
    }
    return [];
  }
});

1
Это может быть сложно, но это действительно полезно. Это в основном работает Plug-N-Play во всех браузерах. Более простое решение может не позволить вам сделать это.
drjorgepolanco

0

Я столкнулся с той же проблемой совсем недавно и решил ее следующим образом:

$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top}, 'fast'});

И ты юпи !!! это работает во всех браузерах.

в случае, если позиционирование не является правильным, вы можете вычесть значение из offset () .top, выполнив это

$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top-desired_value}, 'fast'});

Это решение уже упоминалось в существующих ответах, но спасибо, что поделились
Тони Мишель Коубет,

Этот метод все еще сталкивается с ошибкой в ​​Firefox, как отмечено в вопросе, который я только что опубликовал: stackoverflow.com/q/58617731/1056713
Chaya Cooper

-3

Для меня проблема заключалась в том, что firefox автоматически переходил на якорь с атрибутом name, совпадающим с именем хеша, которое я вставил в URL. Хотя я поставил .preventDefault (), чтобы предотвратить это. Таким образом, после изменения атрибутов имени Firefox не автоматически переходил к якорям, а выполнял анимацию правильно.

@ Тони Извините, если это не понятно. Дело в том, что я изменил хэши в URL, например, www.someurl.com/#hashname. Тогда у меня был, например, такой якорь, <a name="hashname" ...></a>к которому jQuery должен прокручиваться автоматически. Но этого не произошло, потому что он перешел прямо на якорь с соответствующим атрибутом имени в Firefox без анимации прокрутки. После того как я изменил атрибут name на что-то отличное от имени хеша, например на name="hashname-anchor", прокрутка сработала.


-5

Для меня это было предотвращением добавления идентификатора в момент анимации:

Как избежать:

 scrollTop: $('#' + id).offset().top

Подготовьте идентификатор заранее и сделайте это вместо этого:

 scrollTop: $(id).offset().top

Исправлено в FF. (Дополнения CSS не имели никакого значения для меня)


-8
setTimeout(function(){                               
                   $('html,body').animate({ scrollTop: top }, 400);
                 },0);

Надеюсь, это работает.


4
Как setTimeut помогает здесь?
Тони Мишель Каубе

2
@ToniMichelCaubet предположительно цель состоит в том, чтобы сделать анимацию асинхронной. Анимации jQuery уже асинхронные, так что это ничего не дает.
mwcz

Не вдумчивый ответ на проблему. Не уверен, что это будет сделано.
Erier
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.