Top não funciona no firefox

164

Esta função funciona bem. Rola o corpo para o deslocamento de um contêiner desejado

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

Mas não no Firefox. Por quê?

-EDITAR-

Para lidar com o double trigger na resposta aceita, sugiro interromper o elemento antes da animação:

$('body,html').stop(true,true).animate({scrollTop: stop}, delay);
Toni Michel Caubet
fonte
2
Seu código final é o mais elegante de todas as coisas aqui.
Lizardx

Respostas:

331

O Firefox coloca o estouro no htmlnível, a menos que tenha um estilo específico para se comportar de maneira diferente.

Para que ele funcione no Firefox, use

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

Exemplo de trabalho

A solução CSS seria definir os seguintes estilos:

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

Eu diria que a solução JS seria menos invasiva.


Atualizar

Muita discussão abaixo se concentra no fato de que a animação scrollTopde dois elementos faria com que o retorno de chamada fosse invocado duas vezes. Os recursos de detecção de navegador foram sugeridos e subseqüentemente preteridos, e alguns são sem dúvida bastante rebuscados.

Se o retorno de chamada for idempotente e não exigir muito poder de computação, acioná-lo duas vezes pode ser um problema não completo. Se várias chamadas do retorno de chamada forem realmente um problema e se você quiser evitar a detecção de recursos, pode ser mais direto garantir que o retorno de chamada seja executado apenas uma vez a partir do retorno de chamada:

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');
}));
David Hedlund
fonte
51
Solução JS tem uma falha! A função animar é executada duas vezes, para o elemento html e para o elemento body , respectivamente. Parece que os navegadores Webkit usam body e o restante usa html . Esta correção deve funcionar$(jQuery.browser.webkit ? "body": "html").animate(...);
Andreyco 27/12/12
2
Eu descobri que a solução da Andreyco não funcionava no jQuery <1.4 que eu estava usando. Eu era capaz de corrigi-lo assim: $(jQuery.browser.mozilla ? "html" : "body").animate(...);. Seria ótimo não usar o sniffing de navegação, mas a detecção de recursos.
Porém
23
Note, jQuery.browseré obsoleto e faltando a versão mais recente jQuery
spiderplant0
2
4 anos depois, e esta resposta ainda está se mostrando útil. Muito obrigado!
Matt
1
@ DamFa: Principalmente, porque window.scrollnão anima. Você poderia, naturalmente, rolar suas próprias coisas com intervalos e scrollBy. Se você deseja adicionar um alívio a isso, de repente você tem muito código para escrever, para um aspecto menor do seu produto.
precisa
19

A detecção de recursos e a animação em um único objeto suportado seriam boas, mas não há uma solução de uma linha. Enquanto isso, aqui está uma maneira de usar uma promessa de fazer um único retorno de chamada por execução.

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

ATUALIZAÇÃO: veja como você pode usar a detecção de recursos. Esse pedaço de código precisa ser avaliado antes da sua chamada de animação:

// 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();

Agora use o var que acabamos de definir como o seletor da animação de rolagem de página e use a sintaxe regular:

$(scrollTopElement).animate({ scrollTop: 100 }, 500, function() {
    // normal callback
});
Stephen
fonte
Funciona muito bem em circunstâncias normais, obrigado! Existem algumas dicas a serem observadas. (1) Se o corpo não tiver conteúdo suficiente para estourar a janela quando o teste do recurso for executado, a janela não será rolada e o teste retornará um resultado falso de "corpo". (2) Se o teste for executado dentro de um iframe no iOS, ele falhará pelo mesmo motivo. Os iframes no iOS expandem verticalmente (não horizontalmente) até que o conteúdo caiba dentro sem rolar. (3) Como o teste é executado na janela principal, ele aciona os manipuladores de rolagem que já estão no local. ... (continuação)
hashchange
... Por esses motivos, um teste à prova de bala teria que ser executado dentro de um iframe dedicado (resolve (3)) com conteúdo grande o suficiente (resolve (1)) que é testado para rolagem horizontal (resolve (2)).
hashchange
Na verdade, eu tive que executar essa função em um script de tempo limite para fazer com que isso desse resultados consistentes; caso contrário, às vezes parecia retornar htmle às vezes retornar body. Aqui está o meu código depois de declarar a função var scrollTopElement; setTimeout( function() { scrollTopElement = getScrollTopElement(); console.log(scrollTopElement); }, 1000); Obrigado por isso, porém, resolveu o meu problema.
Tony M
Não importa, eu descobri qual é o problema real. Se você já estiver na parte inferior da página quando recarregar (f5), esse script retornará em htmlvez de bodycomo deveria. Isso ocorre apenas se você rolar a página e recarregar, caso contrário, ele funciona.
Tony M
Isso funcionou para mim depois que adicionei uma verificação para ver se a página é aberta na parte inferior.
187 scott
6

Passei anos tentando descobrir por que meu código não funcionaria -

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

O problema estava no meu css -

body { height: 100%};

Eu o configurei para auto(em vez disso, fiquei preocupado com o motivo de ter sido definido 100%). Isso consertou para mim.

Aidan Ewen
fonte
2

Você pode evitar o problema usando um plug-in - mais especificamente, meu plug-in :)

Sério, mesmo que o problema básico tenha sido resolvido há muito tempo (navegadores diferentes usam elementos diferentes para rolar a janela), existem alguns problemas não triviais abaixo da linha que podem atrapalhar você:

Eu sou obviamente tendencioso, mas o jQuery.scrollable é realmente uma boa opção para resolver esses problemas. (Na verdade, eu não conheço nenhum outro plugin que lide com todos eles.)

Além disso, você pode calcular a posição de destino - aquela para a qual você rola - de maneira à prova de balas com a getScrollTargetPosition()função nesta essência .

Tudo o que deixaria você com

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

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

    return false;
}
hashchange
fonte
1

Cuidado com isso. Eu tive o mesmo problema, nem o Firefox nem o Explorer rolando com

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

Então eu usei como David disse

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

Ótimo funcionou, mas novo problema, já que existem dois elementos, corpo e html, a função é executada duas vezes, ou seja, o X roda duas vezes.

tentei apenas com 'html' e o Firefox e o Explorer funcionam, mas agora o Chrome não suporta isso.

Corpo necessário para o Chrome e html para Firefox e Explorer. É um bug do jQuery? não sei.

Apenas tome cuidado com sua função, pois ela será executada duas vezes.

Avenida Gez
fonte
você encontrou uma solução alternativa para isso? E como a detecção do navegador jquery está obsoleta, não sei como posso usar a detecção de recursos para distinguir entre chrome e firefox. A documentação deles não especifica um recurso presente no chrome e não no firefox ou vice-versa. :(
Mandeep Jain,
2
e quanto a .stop (true, true) .animate?
Toni Michel Caubet
0

Eu recomendaria não confiar bodynem htmlcomo uma solução mais portátil. Basta adicionar uma div no corpo que tenha o objetivo de conter os elementos rolados e estilizá-lo para ativar a rolagem em tamanho real:

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

(supondo que display:block;e top:0;left:0;sejam os padrões que correspondem ao seu objetivo) e use $('#my-scroll')para suas animações.

Javarome
fonte
0

Este é o negócio real. Funciona perfeitamente no Chrome e Firefox. É até triste que alguns ignorantes me votem. Esse código literalmente funciona perfeitamente como em todos os navegadores. Você só precisa adicionar um link e colocar o ID do elemento que deseja rolar no href e ele funciona sem especificar nada. Código reutilizável e confiável puro.

$(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
fonte
1
Pode ser complexo, mas é realmente útil. Basicamente, funciona o Plug-N-Play em todos os navegadores. Uma solução mais simples pode não permitir que você faça isso.
drjorgepolanco
0

Encontrei o mesmo problema recentemente e resolvi-o fazendo o seguinte:

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

E você !!! funciona em todos os navegadores.

caso o posicionamento não esteja correto, você pode subtrair um valor do offset () .top fazendo isso

$ ('html, body'). animate ({scrollTop: $ ('. class_of_div'). offset () .top-desired_value}, 'fast'});
Balthazar DOSSOU
fonte
Esta solução já é mencionado nas respostas existentes, mas obrigado por compartilhar
Toni Michel Caubet
Este método ainda encontra uma falha no Firefox, como observado na pergunta que eu só postou: stackoverflow.com/q/58617731/1056713
Chaya Cooper
-3

Para mim, o problema era que o Firefox saltou automaticamente para a âncora com o atributo name igual ao nome do hash que eu coloquei na URL. Mesmo que eu coloque .preventDefault () para evitar isso. Então, após alterar os atributos de nome, o Firefox não pulou automaticamente para as âncoras, mas executou a animação corretamente.

@ Toni Desculpe se isso não era compreensível. O problema é que mudei os hashes no URL como www.someurl.com/#hashname. Então eu tive, por exemplo, uma âncora como <a name="hashname" ...></a>a qual o jQuery deveria rolar automaticamente. Mas não foi porque pulou direto para a âncora com o atributo de nome correspondente no Firefox sem nenhuma animação de rolagem. Depois que alterei o atributo name para algo diferente do nome do hash, por exemplo name="hashname-anchor", a rolagem funcionou.

Peter
fonte
-5

Para mim, estava evitando anexar o ID no ponto da animação:

Evitando:

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

Preparando o ID antecipadamente e fazendo isso:

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

Corrigido no FF. (As adições de CSS não fizeram diferença para mim)

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

Espero que isso funcione.

Simbu
fonte
4
Como o setTimeut ajuda aqui?
Toni Michel Caubet
2
Presumivelmente, o objetivo do @ToniMichelCaubet é tornar a animação assíncrona. As animações do jQuery já são assíncronas, portanto, isso não realiza nada.
Mkcz
Não é uma resposta ponderada para o problema. Não tenho certeza do que isso vai conseguir.
erier