Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle multiple zero-offset Scrollspy elements. #15593

Merged
merged 1 commit into from Mar 2, 2015
Merged

Handle multiple zero-offset Scrollspy elements. #15593

merged 1 commit into from Mar 2, 2015

Conversation

neoeno
Copy link

@neoeno neoeno commented Jan 17, 2015

When the first two elements tracked by Scrollspy have an offset.top of 0, Scrollspy will flip between activating their respective nav events on every scroll event. This causes a visual 'flicker', and heavily degrades scroll performance.

Examples:
Minimal example.
Example with nested sections.

I saw this happen in a system of nested sections positioned hard against the top of the document, e.g.

<section id="animals">
  <section id="dogs">
    Content
  </section>
</section>

The bug is that Scrollspy's check to see if it's at the end of the array of sections uses !arr[index + 1]. This misses the case where arr[index + 1] does exist and is zero.

This commit explicitly checks the array bounds. A unit test is included, but I'm not familiar with bootstrap's testing approach so feedback is appreciated.

(Thanks go to @theroux and @tnguyen14!)

@twbs-savage
Copy link

Tests passed. Automated cross-browser testing via Sauce Labs and Travis CI shows that the JavaScript changes in this pull request are: CONFIRMED

Commit: 22f9e30e990a3d09e3cc585c5ddd5a38729ef1a6
Build details: https://travis-ci.org/twbs-savage/bootstrap/builds/47350532

(Please note that this is a fully automated comment.)

@cvrebert cvrebert added the js label Jan 17, 2015
@cvrebert cvrebert added this to the v3.3.3 milestone Jan 17, 2015
@tnguyen14
Copy link

👍 I ran into this issue yesterday and spent about 5-6 hours trying to debug it. It was really challenging because everything was set up like the doc suggests. I narrowed down the culprit to be using body {margin: 0} set by normalize.css and a :before pseudo-element set by a clearfix technique together.

It took @neoeno an hour reading the source code to figure out the jankiness was happening. It'd be really great to have this fix be incorporated into bootstrap.

}

$.when(testElementsAreActiveAfterScroll())
.then(function () { return testElementsAreActiveAfterScroll() })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and the next line should be indented

@cvrebert
Copy link
Collaborator

LGTM, modulo those nitpicks.
CC: @hnrch02 @XhmikosR for review

@neoeno
Copy link
Author

neoeno commented Feb 26, 2015

Agreed on all counts. Commits added. Can squish before merge (— or not, not sure which is the preferred practice here!)

(offsets[i + 1] === undefined was the required expression though.)

@XhmikosR
Copy link
Member

@neoeno: yeah, fetch, rebase and squash into one.

@kkirsche
Copy link
Contributor

@XhmikosR what's the purpose of fetching before the rebase? Sorry to bring this PR off topic I'm just curious about that. Thanks 😃

@XhmikosR
Copy link
Member

@kkirsche: that's the only way you can bring everything on par with upstream/master.

@kkirsche
Copy link
Contributor

Ah. I always rebase with git rebase -i HEAD~[number of commits we want to squash] [patch-branch name]. That doesn't rebase it against master though. Every time I've tried that the branch ends up with a lot of odd things in it which aren't necessary. Do you have any links to resources I can use to understand how to bring everything to upstream/master?

@cvrebert
Copy link
Collaborator

For rebasing one branch against another, I've always done git checkout branch-to-rebase && git rebase main-branch-to-rebase-against (after having pulled down the latest master when applicable).

@neoeno
Copy link
Author

neoeno commented Feb 26, 2015

Done. I also updated some of the tests to conform to a newer style that had been brought in by @cvrebert — think I did it right, but I'm new to QUnit so some review would be great!

var scrollHeight = $content.scrollTop() + 10
var done = assert.async()
$content.one('scroll', function () {
ok($('#li-1').hasClass('active'), 'element' + element)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be assert.ok(

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done!

@neoeno
Copy link
Author

neoeno commented Feb 28, 2015

@cvrebert Thanks for the comments — I think I understand assert a little better now.

@twbs-savage
Copy link

Tests passed. Automated cross-browser testing via Sauce Labs and Travis CI shows that the JavaScript changes in this pull request are: CONFIRMED

Commit: a9cbfc08c86341a732a758260927ec424e4af5ab
Build details: https://travis-ci.org/twbs-savage/bootstrap/builds/52524203

(Please note that this is a fully automated comment.)

@cvrebert
Copy link
Collaborator

@hnrch02 LGTY?


$.when(testElementsAreActiveAfterScroll())
.then(testElementsAreActiveAfterScroll)
.then(testElementsAreActiveAfterScroll)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this works as you intended - QUnit shows that only two of the planned six assertions are actually made.
If I make this test work like I think you intended (https://gist.github.com/hnrch02/434c11d59153663b999c), the fourth assertion actually fails.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! I'm having a little confusion over this.

The first confusion is that the test in your gist only runs the assertions once. Since on the first pass ++times == 1 and the second pass ++times == 2, which triggers done() before doing an iteration.

When I change the condition to == 4 then the test iterates three times, as the one in this commit does. However, all the assertions pass as expected — I can't seem to reproduce the failure you mention.

I've made a new branch with both tests in, and some hacky logging to show the value of the assertion and the scrollTop. Both tests seem to generate the same output. Here's the branch.

Am I missing something?

I actually think your test, using recursion, looks a fair bit cleaner than this approach using promises. Perhaps at the expense of some readability (for me), but I'm definitely open to either option. I just copied and rewrote the "should add the active class correctly when there are nested elements at 0 scroll offset".

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stand corrected, silly mistake on my end - sorry. The test works fine, would appreciate it though if you replaced this one with the new one from your second branch without the debug code. Thanks!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@twbs-savage
Copy link

Tests passed. Automated cross-browser testing via Sauce Labs and Travis CI shows that the JavaScript changes in this pull request are: CONFIRMED

Commit: 87b0b373d5a444812924188a241390e6396874bc
Build details: https://travis-ci.org/twbs-savage/bootstrap/builds/52672695

(Please note that this is a fully automated comment.)


var $content = $(contentHtml)
.appendTo('#qunit-fixture')
.bootstrapScrollspy({ offset: 0, target: '.navbar' })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For precision, let's use an ID selector here instead of a class selector.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actioned.

When the first two elements in a scrollspy content block have a document
offset of zero (i.e. they're hard against the top of the page),
Scrollspy would switch between them on every scroll event.

This could happen, for example, in a system of nested sections:

```
<section id="animals">
  <section id="dogs">
	Content
  </section>
</section>
```

This ocurred because Scrollspy's check to see if it's at the end of the
array of sections uses `!arr[index]`. This misses the case where
`arr[index]` does exist and is zero.

This commit explicitly checks the array bounds.
@twbs-savage
Copy link

Tests passed. Automated cross-browser testing via Sauce Labs and Travis CI shows that the JavaScript changes in this pull request are: CONFIRMED

Commit: a1aa0f8
Build details: https://travis-ci.org/twbs-savage/bootstrap/builds/52677790

(Please note that this is a fully automated comment.)

cvrebert added a commit that referenced this pull request Mar 2, 2015
…ollspy_elements

Handle multiple zero-offset Scrollspy elements.
@cvrebert cvrebert merged commit 015e63f into twbs:master Mar 2, 2015
@cvrebert
Copy link
Collaborator

cvrebert commented Mar 2, 2015

@neoeno Thank you!

@neoeno
Copy link
Author

neoeno commented Mar 2, 2015

My pleasure! ✨

@cvrebert cvrebert mentioned this pull request Mar 2, 2015
@twbs twbs locked and limited conversation to collaborators Mar 2, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants