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;
}
hashchange
источник
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')для анимации.

Javarome
источник
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 [];
  }
});
drjorgepolanco
источник
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);

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

Simbu
источник
4
Как setTimeut помогает здесь?
Тони Мишель Каубе
2
@ToniMichelCaubet предположительно цель состоит в том, чтобы сделать анимацию асинхронной. Анимации jQuery уже асинхронные, так что это ничего не дает.
mwcz
Не вдумчивый ответ на проблему. Не уверен, что это будет сделано.
Erier