Przyklejony pasek boczny: trzymaj się dołu podczas przewijania w dół, do góry podczas przewijania w górę

97

Od jakiegoś czasu szukałem rozwiązania mojego problemu z lepkim paskiem bocznym. Mam konkretny pomysł, jak chciałbym, żeby to działało; efektywnie chciałbym, aby przy przewijaniu w dół przyklejał się do dołu, a gdy tylko przewiniesz z powrotem w górę, chciałbym, aby przylegał do góry płynnym ruchem (bez przeskakiwania). Nie jestem w stanie znaleźć przykładu tego, co próbuję osiągnąć, dlatego stworzyłem obraz, który mam nadzieję lepiej zilustruje ten punkt:

Przyklejony pasek boczny: trzymaj się dołu podczas przewijania w dół, do góry podczas przewijania w górę

  1. Pasek boczny znajduje się pod nagłówkiem.
  2. Podczas przewijania w dół pasek boczny pozostaje na poziomie zawartości strony, dzięki czemu można przewijać zarówno pasek boczny, jak i zawartość.
  3. Sięgnij do dołu paska bocznego, pasek boczny przykleja się do dołu widoku (większość wtyczek pozwala tylko na przyklejanie się do góry, niektóre, które pozwalają na trzymanie się do dołu, nie pozwalają na oba).
  4. Sięgnij na dół, pasek boczny znajduje się nad stopką.
  5. Podczas przewijania w górę pasek boczny pozostaje na poziomie zawartości, dzięki czemu można ponownie przewijać zawartość i pasek boczny.
  6. Osiągnij górę paska bocznego, pasek boczny przylega do górnej części widoku.
  7. Dotrzyj do góry, a pasek boczny znajduje się z powrotem pod nagłówkiem.

Mam nadzieję, że to wystarczająca informacja. Utworzyłem jsfiddle, aby przetestować wszelkie wtyczki / skrypty, które zresetowałem dla tego pytania: http://jsfiddle.net/jslucas/yr9gV/2/ .

andbamnan
źródło

Odpowiedzi:

27

+1 do bardzo ładnego i ilustracyjnego zdjęcia.

Wiem, że to stare pytanie, ale przypadkowo znalazłem to samo pytanie, które zadałeś na forum.jquery.com i jedną odpowiedź tam (autorstwa @ tucker973) , zasugerowałem jedną fajną bibliotekę do zrobienia tego i chciałem się nim tutaj podzielić.

Nazywa się sticky-kit przez @leafo

Tutaj masz kod bardzo podstawowego przykładu, który przygotowałem i działające demo, aby zobaczyć wynik.

Oczywiście wszystkie kredyty należą do twórcy wtyczki, zrobiłem ten przykład tylko po to, aby go tutaj pokazać. Muszę osiągnąć ten sam rezultat, o którym myśleliście, i uznałam tę wtyczkę za bardzo przydatną.

gmo
źródło
Używam tej małej wtyczki, jak sugerujesz, ale szerokość zmienia się po tym, jak przyklei się do góry. Jak mogę to zrobić bez zmian?
vijayrana
Cześć @gmo! Szukam tego samego, ale nie działa (nie przykleja się do góry przy przewijaniu w górę), gdy pasek przewijania jest dłuższy niż rzutnia ...
Igor Laszlo
13

Dzięki za świetną grafikę. Szukałem też rozwiązania tego wyzwania!

Niestety, inna zamieszczona tutaj odpowiedź nie dotyczy wymagania nr 5, który określa możliwość płynnego przewijania paska bocznego.

Stworzyłem skrzypce, które realizują wszystkie wymagania: http://jsfiddle.net/bN4qu/5/

Podstawowa logika, którą należy zaimplementować, to:

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

W skrzypcach używam transformacji CSS3 do przenoszenia elementu docelowego, więc nie zadziała np. W IE <9. Logika jest jednak słuszna dla zastosowania innego podejścia.

Zmodyfikowałem również twoje skrzypce, aby lepki pasek boczny miał gradientowe tło. Pomaga to pokazać, że przejawia się właściwe zachowanie.

Mam nadzieję, że komuś się to przyda!

Travis Kriplean
źródło
2
Dla każdego, kto szuka odpowiedzi, ta autorstwa Travisa jest najbardziej bezbłędną, jaką do tej pory znalazłem. Dzięki.
marcovega
Świetna próba, po prostu zadziałało, kiedy to upuściłem, co jest czymś więcej, niż mogłem powiedzieć o innych wtyczkach :) Wydajność odniosła duży sukces, ale myślę, że jest to prawie podane w przypadku każdej nienatywnej, lepkiej implementacji.
jClark
To był doskonały punkt wyjścia! Zapakowałem $.cssfunkcję w a requestAnimationFramei dodałem funkcję niszczenia / rozpinania do użytku w nowoczesnych frameworkach frontendowych, takich jak vue / react. Po tym wydajność absolutnie nie jest problemem!
Christophe Marois
@Cristophe Marois. Czy możesz podać przykład na jsfiddle?
Darme,
dzięki, ale ten kod nie działa dla małego paska bocznego, który jest krótszy od widoku (wysokość widoku)
Seyed Abbas Seyedi
12

Oto przykład, jak to zaimplementować:

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/

Działa to zgodnie z oczekiwaniami we wszystkich scenariuszach i jest również dobrze obsługiwane w IE.

Anoop Naik
źródło
zobacz tę odpowiedź i wyjaśnij stackoverflow.com/questions/28428327/ ...
theinlwin
@Anoop Naik - to prawie dobrze, czego szukam ... sticky-kit nie działa dla pasków bocznych, które są dłuższe niż widok, twój działa. Chciałbym jednak odwrotnie: kiedy przewijam w dół, przykleja się do góry, a przewijając w górę, przykleja się do dołu ... czy możesz mi pomóc, proszę, przy tej małej zmianie skrzypiec?
Igor Laszlo
1
@IgorLaszlo pewnie, daj mi trochę czasu, zaktualizuję cię za jakiś czas ...
Anoop Naik
To również wyjaśnia mój problem: „Gdy element z pozycją: lepki jest„ zablokowany ”i jest dłuższy niż rzutnia, jego zawartość można zobaczyć dopiero po przewinięciu do dołu kontenera. Byłoby fajnie, gdyby element„ zablokowany ”przewinął się z dokumentem i zatrzymany, gdy osiągnie dolną krawędź. Jeśli użytkownik przewinie wstecz, to samo wydarzy się ponownie, ale w odwrotnej kolejności. " - napisane przez inną osobę, która ma ten sam problem ( stackoverflow.com/questions/47618271/… )
Igor Laszlo
@Anoop Naik! Dziękuję za twój wysiłek, ale proszę, znalazłem wtyczkę Sticky jquery, aby rozwiązać mój problem: abouolia.github.io/sticky-sidebar Jeszcze raz dziękuję!
Igor Laszlo
0

Szukałem dokładnie tego samego. Najwyraźniej musiałem poszukać niejasnych terminów, aby znaleźć podobne pytanie z grafiką. Okazuje się, że właśnie tego szukałem. Nie mogłem znaleźć żadnych wtyczek, więc postanowiłem zrobić to sam. Miejmy nadzieję, że ktoś to zobaczy i dopracuje.

Oto szybki i brudny przykładowy kod HTML, którego używam.

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

Oto jQuery, które stworzyłem:

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

    });
}

Kilka uwag:

  • .rectbtfad jest najniższym elementem na moim pasku bocznym
  • Używam wysokości mojego #masthead, ponieważ jest to lepki nagłówek, więc musi to skompensować
  • Jest sprawdzanie dla col-2 float, ponieważ używam responsywnego projektu i nie chcę, aby aktywował się na mniejszych ekranach

Byłoby wspaniale, gdyby ktoś mógł to nieco bardziej udoskonalić.

callmeforsox
źródło
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");

Przykład roboczy: https://jsfiddle.net/L7xoopst/6/

SezginOnline
źródło
dodać trochę wyjaśnienia?
HaveNoDisplayName
Jeśli zaktualizujesz wysokość w lepkim przedmiocie,
wystąpią
0

W repozytorium Wordpress jest stosunkowo nieznana wtyczka, znana jako WP Sticky Sidebar. Wtyczka robi dokładnie to, co chciałeś (Lepki pasek boczny: trzymaj się dołu podczas przewijania w dół, do góry podczas przewijania w górę) WP Sticky Sidebar Wordpress Link do repozytorium: https://wordpress.org/plugins/mystickysidebar/

Manju Talluri
źródło
Dzięki za informację! Działał doskonale. To zabawne, że grafika przedstawiająca zachowanie jest taka sama dla wyróżnionego obrazu wtyczki :)
Oksana Romaniv
0

Przykładowy dwukierunkowy Sticky Sidebar.

Jeśli ktoś potrzebuje lekkiego rozwiązania nie opartego na jQuery, zapraszam do zapoznania się z tym kodem: Two-direction-Sticky-Sidebar na GitHubie .

//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
źródło