Barra lateral fixa: fixa na parte inferior ao rolar para baixo e na parte superior ao rolar para cima

97

Já faz algum tempo que procuro uma solução para o meu problema de barra lateral pegajosa. Tenho uma ideia específica de como gostaria que ele atuasse; efetivamente, gostaria que ficasse na parte inferior à medida que você rola para baixo e, assim que você rolar de volta para cima, gostaria que ficasse no topo, em um movimento fluido (sem pulos). Não consigo encontrar um exemplo do que estou tentando alcançar, então criei uma imagem que espero ilustrar o ponto de forma mais clara:

Barra lateral fixa: fixa na parte inferior ao rolar para baixo e na parte superior ao rolar para cima

  1. A barra lateral fica sob o cabeçalho.
  2. Conforme você rola para baixo, a barra lateral permanece nivelada com o conteúdo da página para que você possa rolar tanto pela barra lateral quanto pelo conteúdo.
  3. Alcance a parte inferior da barra lateral, a barra lateral adere à parte inferior da janela de visualização (a maioria dos plug-ins permite apenas a fixação no topo, alguns que permitem a fixação na parte inferior não permitem os dois).
  4. Chegue ao fundo, a barra lateral fica acima do rodapé.
  5. Conforme você rola para cima, a barra lateral permanece nivelada com o conteúdo para que você possa rolar pelo conteúdo e pela barra lateral novamente.
  6. Chegue ao topo da barra lateral, a barra lateral adere ao topo da janela de visualização.
  7. Alcance o topo e a barra lateral ficará abaixo do cabeçalho.

Espero que seja informação suficiente. Eu criei um jsfiddle para testar quaisquer plug-ins / scripts, que eu redefini para esta questão: http://jsfiddle.net/jslucas/yr9gV/2/ .

andbamnan
fonte

Respostas:

27

+1 para a imagem muito bonita e ilustrativa.

Eu sei que é uma pergunta antiga, mas casualmente encontrei a mesma pergunta postada por você em forum.jquery.com e uma resposta lá (por @ tucker973) , sugeri uma boa biblioteca para fazer isso e queria compartilhá-la aqui.

É chamado de sticky-kit por @leafo

Aqui você tem o código de um exemplo muito básico que preparei e uma demonstração de trabalho para ver o resultado.

Claro, todos os créditos vão para o criador do plugin, só fiz esse exemplo para mostrar aqui. Eu preciso conseguir o mesmo resultado que você estava procurando e achei este plugin muito útil.

gmo
fonte
Eu uso este pequeno plugin como você sugere, mas a largura muda depois que ele cola no topo. Como posso mantê-lo inalterado?
vijayrana de
Olá @gmo! Estou procurando a mesma coisa, mas não funciona (não gruda no topo ao rolar para cima) quando a barra de rolagem é maior do que a janela de visualização ...
Igor Laszlo
13

Obrigado pelo ótimo gráfico. Também procurava uma solução para este desafio!

Infelizmente, a outra resposta postada aqui não atende ao requisito nº 5, que estipula a capacidade de rolar de volta pela barra lateral sem problemas.

Criei um violino que implementa todos os requisitos: http://jsfiddle.net/bN4qu/5/

A lógica central que precisa ser implementada é:

If scrolling up OR the element is shorter than viewport Then
  Set top of element to top of viewport If scrolled above top of element
If scrolling down then
  Set bottom of element at bottom of viewport If scrolled past bottom of element

No violino, eu uso a transformação CSS3 para mover o elemento de destino, por isso não funcionará, por exemplo, no IE <9. A lógica é válida para usar uma abordagem diferente.

Além disso, modifiquei seu violino para que a barra lateral aderente tenha um fundo gradiente. Isso ajuda a mostrar que o comportamento adequado está sendo exibido.

Espero que isso seja útil para alguém!

Travis Kriplean
fonte
2
Para quem está procurando por uma resposta, este de Travis é o mais perfeito que encontrei até agora. Obrigado cara.
marcovega
Uma grande tentativa, basicamente funcionou quando coloquei isso, o que é mais do que eu poderia dizer sobre outros plug-ins :) O desempenho sofreu um grande golpe, mas acho que isso é praticamente um dado com qualquer implementação sticky não nativa.
jClark de
Este foi um excelente ponto de partida! Eu envolvi a $.cssfunção em a requestAnimationFramee adicionei uma função destroy / unbind para uso em frameworks front-end modernos como vue / react. O desempenho não é um problema depois disso!
Christophe Marois
@Cristophe Marois você pode fornecer um exemplo no jsfiddle, por favor?
Darme
obrigado, mas este código não funciona para uma pequena barra lateral que é menor da janela de visualização (altura da janela de visualização)
Seyed Abbas Seyedi
12

Aqui está um exemplo de como implementar isso:

JavaScript:

$(function() {

var $window = $(window);
var lastScrollTop = $window.scrollTop();
var wasScrollingDown = true;

var $sidebar = $("#sidebar");
if ($sidebar.length > 0) {

    var initialSidebarTop = $sidebar.position().top;

    $window.scroll(function(event) {

        var windowHeight = $window.height();
        var sidebarHeight = $sidebar.outerHeight();

        var scrollTop = $window.scrollTop();
        var scrollBottom = scrollTop + windowHeight;

        var sidebarTop = $sidebar.position().top;
        var sidebarBottom = sidebarTop + sidebarHeight;

        var heightDelta = Math.abs(windowHeight - sidebarHeight);
        var scrollDelta = lastScrollTop - scrollTop;

        var isScrollingDown = (scrollTop > lastScrollTop);
        var isWindowLarger = (windowHeight > sidebarHeight);

        if ((isWindowLarger && scrollTop > initialSidebarTop) || (!isWindowLarger && scrollTop > initialSidebarTop + heightDelta)) {
            $sidebar.addClass('fixed');
        } else if (!isScrollingDown && scrollTop <= initialSidebarTop) {
            $sidebar.removeClass('fixed');
        }

        var dragBottomDown = (sidebarBottom <= scrollBottom && isScrollingDown);
        var dragTopUp = (sidebarTop >= scrollTop && !isScrollingDown);

        if (dragBottomDown) {
            if (isWindowLarger) {
                $sidebar.css('top', 0);
            } else {
                $sidebar.css('top', -heightDelta);
            }
        } else if (dragTopUp) {
            $sidebar.css('top', 0);
        } else if ($sidebar.hasClass('fixed')) {
            var currentTop = parseInt($sidebar.css('top'), 10);

            var minTop = -heightDelta;
            var scrolledTop = currentTop + scrollDelta;

            var isPageAtBottom = (scrollTop + windowHeight >= $(document).height());
            var newTop = (isPageAtBottom) ? minTop : scrolledTop;

            $sidebar.css('top', newTop);
        }

        lastScrollTop = scrollTop;
        wasScrollingDown = isScrollingDown;
    });
}
});

CSS:

#sidebar {
  width: 180px;
  padding: 10px;
  background: red;
  float: right;
}

.fixed {
  position: fixed;
  right: 50%;
  margin-right: -50%;
}

Demo: http://jsfiddle.net/ryanmaxwell/25QaE/

Isso funciona conforme o esperado em todos os cenários e também é bem suportado no IE.

Anoop Naik
fonte
veja esta resposta e explique stackoverflow.com/questions/28428327/…
theinlwin
@Anoop Naik - isso é quase bom o que estou procurando ... sticky-kit não funciona para barras laterais maiores do que a janela de visualização, a sua funciona. No entanto, eu gostaria do contrário: quando eu rolar para baixo, ele gruda no topo, e ao rolar para cima, ele gruda na parte inferior ... você pode me ajudar com aquela pequena mudança em um violino, por favor?
Igor Laszlo
1
@IgorLaszlo com certeza, me dê um tempo, atualizarei você em algum momento ...
Anoop Naik
Isso também explica meu problema: "Quando o elemento com posição: aderente está" preso "e é mais longo que a janela de visualização, você só pode ver seu conteúdo depois de rolar para o fundo do contêiner. Seria legal se o elemento" preso "rolasse com o documento e parado, assim que atingir sua borda inferior. Se o usuário rolar para trás, a mesma coisa acontecerá novamente, mas ao contrário. " - escrito por outra pessoa que tem o mesmo problema ( stackoverflow.com/questions/47618271/… )
Igor Laszlo
@Anoop Naik! Obrigado pelo seu esforço, mas por favor, encontrei o plugin Sticky jquery para resolver meu problema: abouolia.github.io/sticky-sidebar Obrigado novamente!
Igor Laszlo
0

Eu estava procurando exatamente a mesma coisa. Aparentemente, precisei pesquisar alguns termos obscuros apenas para encontrar uma pergunta semelhante com o gráfico. Acontece que é exatamente o que eu estava procurando. Não consegui encontrar nenhum plug-in, então decidi fazer sozinho. Espero que alguém veja isso e o refine.

Aqui está um exemplo rápido de html que estou usando.

<div id="main">
    <div class="col-1">
    </div>
    <div class="col-2">
        <div class="side-wrapper">
            sidebar content
        </div>
    </div>
</div>

Aqui está o jQuery que fiz:

var lastScrollPos = $(window).scrollTop();
var originalPos = $('.side-wrapper').offset().top;
if ($('.col-2').css('float') != 'none') {
    $(window).scroll(function(){
        var rectbtfadPos = $('.rectbtfad').offset().top + $('.rectbtfad').height();
        // scroll up direction
        if ( lastScrollPos > $(window).scrollTop() ) {
            // unstick if scrolling the opposite direction so content will scroll with user
            if ($('.side-wrapper').css('position') == 'fixed') {
                $('.side-wrapper').css({
                    'position': 'absolute',
                    'top': $('.side-wrapper').offset().top + 'px',
                    'bottom': 'auto'
                });
            } 
            // if has reached the original position, return to relative positioning
            if ( ($(window).scrollTop() + $('#masthead').height()) < originalPos ) {
                $('.side-wrapper').css({
                    'position': 'relative',
                    'top': 'auto',
                    'bottom': 'auto'
                });
            } 
            // sticky to top if scroll past top of sidebar
            else if ( ($(window).scrollTop() + $('#masthead').height()) < $('.side-wrapper').offset().top && $('.side-wrapper').css('position') == 'absolute' ) {
                $('.side-wrapper').css({
                    'position': 'fixed',
                    'top': 15 + $('#masthead').height() + 'px', // padding to compensate for sticky header
                    'bottom': 'auto'
                });
            }
        } 
        // scroll down
        else {
            // unstick if scrolling the opposite direction so content will scroll with user
            if ($('.side-wrapper').css('position') == 'fixed') {
                $('.side-wrapper').css({
                    'position': 'absolute',
                    'top': $('.side-wrapper').offset().top + 'px',
                    'bottom': 'auto'
                });
            } 
            // check if rectbtfad (bottom most element) has reached the bottom
            if ( ($(window).scrollTop() + $(window).height()) > rectbtfadPos && $('.side-wrapper').css('position') != 'fixed' ) {
                $('.side-wrapper').css({
                    'width': $('.col-2').width(),
                    'position': 'fixed',
                    'bottom': '0',
                    'top': 'auto'
                });
            }
        }
        // set last scroll position to determine if scrolling up or down
        lastScrollPos = $(window).scrollTop();

    });
}

Algumas notas:

  • .rectbtfad é o elemento mais abaixo na minha barra lateral
  • Estou usando a altura do meu #masthead porque é um cabeçalho fixo e precisa compensar
  • Há uma verificação de col-2 float, pois estou usando um design responsivo e não quero que seja ativado em telas menores

Se alguém puder refinar isso um pouco mais, seria ótimo.

Callmeforsox
fonte
0
function fixMe(id) {
    var e = $(id);
    var lastScrollTop = 0;
    var firstOffset = e.offset().top;
    var lastA = e.offset().top;
    var isFixed = false;
    $(window).scroll(function(event){
        if (isFixed) {
            return;
        }
        var a = e.offset().top;
        var b = e.height();
        var c = $(window).height();
        var d = $(window).scrollTop();
        if (b <= c - a) {
            e.css({position: "fixed"});
            isFixed = true;
            return;
        }           
        if (d > lastScrollTop){ // scroll down
            if (e.css("position") != "fixed" && c + d >= a + b) {
                e.css({position: "fixed", bottom: 0, top: "auto"});
            }
            if (a - d >= firstOffset) {
                e.css({position: "absolute", bottom: "auto", top: lastA});
            }
        } else { // scroll up
            if (a - d >= firstOffset) {
                if (e.css("position") != "fixed") {
                    e.css({position: "fixed", bottom: "auto", top: firstOffset});
                }
            } else {
                if (e.css("position") != "absolute") {
                    e.css({position: "absolute", bottom: "auto", top: lastA});
                }               
            }
        }
        lastScrollTop = d;
        lastA = a;
    });
}

fixMe("#stick");

Exemplo de trabalho: https://jsfiddle.net/L7xoopst/6/

SezginOnline
fonte
adicionar um pouco de explicação?
HaveNoDisplayName
Se você atualizar a altura dentro do item fixo, isso terá alguns problemas
Callam
0

Existe um plugin relativamente desconhecido no repositório do Wordpress conhecido como WP Sticky Sidebar. O plugin faz exatamente o que você queria (barra lateral aderente: ficar na parte inferior ao rolar para baixo, superior ao rolar para cima) WP Sticky Sidebar Repositório Wordpress Link: https://wordpress.org/plugins/mystickysidebar/

Manju Talluri
fonte
Obrigado pela informação! Funcionou perfeitamente. É engraçado que o gráfico da ilustração do comportamento seja o mesmo para a imagem em destaque do plugin :)
Oksana Romaniv
0

Amostra da barra lateral aderente de duas direções.

Se alguém precisa de uma solução leve não baseada em jQuery, convido você a se familiarizar com este código: Two-direction-Sticky-Sidebar no GitHub .

//aside selector
const aside = document.querySelector('[data-sticky="true"]'), 
//varibles
startScroll = 0;
var endScroll = window.innerHeight - aside.offsetHeight -500,
currPos = window.scrollY;
screenHeight = window.innerHeight,
asideHeight = aside.offsetHeight;
aside.style.top = startScroll + 'px';
//check height screen and aside on resize
window.addEventListener('resize', ()=>{
    screenHeight = window.innerHeight;
    asideHeight = aside.offsetHeight;
});
//main function
document.addEventListener('scroll', () => {
    endScroll = window.innerHeight - aside.offsetHeight;
    let asideTop = parseInt(aside.style.top.replace('px;', ''));
    if(asideHeight>screenHeight){
        if (window.scrollY < currPos) {
            //scroll up
            if (asideTop < startScroll) {
                aside.style.top = (asideTop + currPos - window.scrollY) + 'px';
            } else if (asideTop >= startScroll && asideTop != startScroll) {
                aside.style.top = startScroll + 'px';
            }
        } else {
            //scroll down
            if (asideTop > endScroll) {
                aside.style.top = (asideTop + currPos - window.scrollY) + 'px';
            } else if (asideTop < (endScroll) && asideTop != endScroll) {
                aside.style.top = endScroll + 'px';
            }
        }
    }
    currPos = window.scrollY;
}, {
    capture: true,
    passive: true
});
body{
  padding: 0 20px;
}
#content {
  height: 2000px;
}
header {
  width: 100%;
  height: 150px;
  background: #aaa;
}
main {
  float: left;
  width: 65%;
  height: 100%;
  background: #444;
}
aside {
  float: right;
  width: 30%;
  position: sticky;
  top: 0px;
  background: #777;
}
li {
  height: 50px;
}
footer {
  width: 100%;
  height: 300px;
  background: #555;
  position: relative;
  bottom: 0;
}
<!DOCTYPE html>
    <head>
        <link href="/src/style.css" rel="preload" as="style"/>
    </head>
    <body>
        <header>Header</header>
            <div id="content">
            <main>Content</main>
            <aside data-sticky="true">
              <lu>
                <li>Top</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>Bottom</li>
              </lu>
            </aside>
            </div>
        <footer>Footer</footer>
        <script src='/src/script.js' async></script>
    </body>
</html>

Krzysztof AN
fonte