How to use multiple target on body with Bootstrap ScrollSpy
Asked Answered
T

3

6

I want to use Bootstrap ScrollSpy with multiple nav, my main nav and a vertical subnav.

I tried multiple thing like :

  • <body data-spy="scroll" data-target="#main-nav #subnav">
  • <body data-spy="scroll" data-target="#main-nav, #subnav">
  • $('body').scrollspy({target: "#main-nav"});
    $('body').scrollspy({target: "#subnav"});
  • $('body').scrollspy({target: "#main-nav"}, {target: "#subnav"});
  • Putting wrapper div:
    <body data-spy="scroll" data-target="#main-nav"><div data-spy="scroll" data-target="#subnav">

but nothing worked fine...

How can I acheive this?

Thank you!

Twilley answered 28/5, 2014 at 17:26 Comment(3)
you cant have multiple body tag in one page .. use a real subnav with ons scrollspy can you paste you nav html in a fiddle i maybe be able to helpAquino
I dont have multiple body tag, I want to add multiple data-target inside body tag for using it with Bootstrap ScrollSpy.Twilley
i know can you put paste your css as well.Aquino
T
12

Ok, I found a way to acheive what I want.

If I dont define a target to scrollSpy, all my navigation links are considered :

$('body').scrollspy({target: ''});
Twilley answered 29/5, 2014 at 16:4 Comment(0)
A
1

I know, that this question is very old, but here my implementation for multiple target scroll-spy. Just use it like this: <body data-multiple-spy="scroll" data-target="#bs-example-navbar-collapse-1,#features-navbar .navbar-collapse" data-offset="70"</body>

Plugin:

+function ($) {
'use strict';

// Multiple SCROLLSPY CLASS DEFINITION
// ===================================

function MultipleScrollSpy(element, options) {
    this.$body = $(document.body)
    this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
    this.options = $.extend({}, MultipleScrollSpy.DEFAULTS, options)
    var target = (this.options.target || '').split(',');
    this.selector = target.map(function (s) { return s + ' .nav li > a' })
    this.offsets = []
    this.targets = []
    this.activeTarget = []
    this.scrollHeight = 0

    this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
    this.refresh()
    this.process()
}

MultipleScrollSpy.VERSION = '0.0.1'

MultipleScrollSpy.DEFAULTS = {
    offset: 10
}

MultipleScrollSpy.prototype.getScrollHeight = function () {
    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
}

MultipleScrollSpy.prototype.refresh = function () {
    var that = this
    var offsetMethod = 'offset'
    var offsetBase = 0

    this.offsets = []
    this.targets = []
    this.activeTarget = []
    this.scrollHeight = this.getScrollHeight()

    if (!$.isWindow(this.$scrollElement[0])) {
        offsetMethod = 'position'
        offsetBase = this.$scrollElement.scrollTop()
    }

    for (var i = 0; i < this.selector.length; i++) {
        that.offsets[i] = [];
        that.targets[i] = [];
        this.$body
            .find(this.selector[i])
            .map(function () {
                var $el = $(this)
                var href = $el.data('target') || $el.attr('href')
                var $href = /^#./.test(href) && $(href)

                return ($href &&
                    $href.length &&
                    $href.is(':visible') &&
                    [[$href[offsetMethod]().top + offsetBase, href]]) ||
                    null
            })
            .sort(function (a, b) { return a[0] - b[0] })
            .each(function () {
                that.offsets[i].push(this[0])
                that.targets[i].push(this[1])
            })
    }
}

MultipleScrollSpy.prototype.process = function () {
    for (var k = 0; k < this.selector.length; k++) {
        var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
        var scrollHeight = this.getScrollHeight()
        var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
        var offsets = this.offsets[k] || []
        var targets = this.targets[k] || []
        var activeTarget = this.activeTarget[k]
        var i

        if (this.scrollHeight != scrollHeight) {
            this.refresh()
        }

        if (scrollTop >= maxScroll) {
            return activeTarget[k] != (i = targets[targets.length - 1]) && this.activate(k, i)
        }

        if (activeTarget && scrollTop < offsets[0]) {
            this.activeTarget[k] = null
            return this.clear(k)
        }

        for (i = offsets.length; i--;) {
            activeTarget != targets[i] &&
                scrollTop >= offsets[i] &&
                (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) &&
                this.activate(k, targets[i])
        }
    }
}

MultipleScrollSpy.prototype.activate = function (index, target) {
    this.activeTarget[index] = target

    this.clear(index)

    var selector = this.selector[index] +
        '[data-target="' + target + '"],' +
        this.selector[index] + '[href="' + target + '"]'

    var active = $(selector)
        .parents('li')
        .addClass('active')

    if (active.parent('.dropdown-menu').length) {
        active = active
            .closest('li.dropdown')
            .addClass('active')
    }

    active.trigger('activate.bs.scrollspy')
}

MultipleScrollSpy.prototype.clear = function (index) {
    $(this.selector[index])
        .parentsUntil(this.options.target, '.active')
        .removeClass('active')
}


// MULTIPLE SCROLLSPY PLUGIN DEFINITION
// ====================================

function Plugin(option) {
    return this.each(function () {
        var $this = $(this)
        var data = $this.data('bs.scrollspy')
        var options = typeof option == 'object' && option

        if (!data) $this.data('bs.scrollspy', (data = new MultipleScrollSpy(this, options)))
        if (typeof option == 'string') data[option]()
    })
}

var old = $.fn.multipleScrollspy;

$.fn.multipleScrollspy = Plugin;
$.fn.multipleScrollspy.Constructor = MultipleScrollSpy;


    // MULTIPLE SCROLLSPY NO CONFLICT
    // =============================

    ($.fn.multipleScrollspy || {}).noConflict = function () {
        $.fn.multipleScrollspy = old
        return this
    }


// MULTIPLE SCROLLSPY DATA-API
// ===========================

$(window).on('load.bs.scrollspy.data-api', function () {
    $('[data-multiple-spy="scroll"]').each(function () {
        var $spy = $(this)
        Plugin.call($spy, $spy.data())
    })
})

}(jQuery);
Andrel answered 12/7, 2018 at 20:59 Comment(0)
C
1

And yet another answer to a very old question.

I have a vertical navbar that shows on the left of my website, for a wide screen the #navbar is wide with icons and text and it uses the ScrollSpy to highlight which section you're currently scrolled to. For smaller screens the #navbar-small is icons only, no text so it's narrower, it's implemented using bootstrap break points to hide the small bar when the screen is wide and vice versa.

Using a MutationObserver (which was either not available or very shiny new when this question was originally posted) you can set a callback to be run when a target node and optionally it's children change, so my work around is to target the wide #navbar which the ScrollSpy targets on my site, and then use its changes to add/remove the active class to my #navbar-small elements children as required (I'm actually just copying all the class attributes from #navbar to #navbar-small because for me they are the same).

So my Solution:

// Make this function do whatever logic you need it to do, I'm lucky that I just
// want to copy the exact same classes from #navbar's children to
// #navbar-small's children.
//
const duplicateScrollSpyNavActive = function () {
  let navbarSmallLinks = document
    .getElementById("navbar-small")
    .getElementsByClassName("nav-link");

  for (let link of navbarSmallLinks) {
    link.setAttribute(
      "class",
      document
        .getElementById(link.getAttribute("id").replace("small-", ""))
        .getAttribute("class"),
    );
  }
};

// This is the mostly generic part, just make duplicateScrollSpyNavActive above
// do whatever you want for your use-case.
window.addEventListener("load", function (event) {
  //
  // Make target be the element id that is the same as `data-bs-target` in your
  // HTML
  //
  const targetNode = document.getElementById("navbar");

  // We only really care about the active class being added or removed so we
  // can just filter for "class" but we need subtree to be true so we can
  // detect changes on the children (the actual links that get highlighted).
  //
  const options = { subtree: true, attributeFilter: ["class"] };
  const observer = new MutationObserver(duplicateScrollSpyNavActive);

  observer.observe(targetNode, options);
});

Crowfoot answered 13/1 at 22:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.