Repository: WickyNilliams/headroom.js
Branch: master
Commit: 0f53fc75f41b
Files: 21
Total size: 41.9 KB
Directory structure:
gitextract_56e1gft5/
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── cypress/
│ ├── .eslintrc
│ ├── fixtures/
│ │ └── index.html
│ ├── integration/
│ │ └── headroom.spec.js
│ ├── plugins/
│ │ └── index.js
│ └── support/
│ ├── commands.js
│ └── index.js
├── cypress.json
├── package.json
├── rollup.config.js
└── src/
├── .eslintrc
├── Headroom.js
├── angular.headroom.js
├── features.js
├── jQuery.headroom.js
├── scroller.js
└── trackScroll.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc
================================================
{
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"env": {
"browser": true,
"node": true
},
"extends": ["eslint:recommended"],
"rules": {
"no-unused-expressions": "error",
"no-bitwise": "error",
"camelcase": "error",
"curly": "error",
"eqeqeq": "error",
"wrap-iife": ["error", "any"],
"no-use-before-define": [
"error",
{
"functions": false
}
],
"linebreak-style": "error",
"new-cap": "error",
"no-caller": "error",
"quotes": ["error", "double"],
"indent": [
"error",
2,
{
"SwitchCase": 1
}
]
}
}
================================================
FILE: .gitignore
================================================
node_modules
dist/
.vscode
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "10"
addons:
apt:
packages:
# Ubuntu 16+ does not install this dependency by default, so we need to install it ourselves
- libgconf-2-4
cache:
# Caches $HOME/.npm when npm ci is default script command
# Caches node_modules in all other cases
npm: true
directories:
# we also need to cache folder with Cypress binary
- ~/.cache
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2013 Nick Williams
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# Headroom.js
Headroom.js is a lightweight, high-performance JS widget (with no dependencies) that allows you to react to the user's scroll. The header on [this site](http://wicky.nillia.ms/headroom.js) is a living example, it slides out of view when scrolling down and slides back in when scrolling up.
## Installation
Headroom.js is available on npm. To install:
```bash
npm install headroom.js --save
# or...
yarn add headroom.js
```
A a universal build (suitable for script tags, CommonJS, and AMD) is available from unpkg.com:
[https://unpkg.com/headroom.js](https://unpkg.com/headroom.js)
## Documentation
For complete documentation please visit the [headroom.js website](http://wicky.nillia.ms/headroom.js).
## Quick start
After installing `headroom.js`. The following JS will create and initialise a headroom instance:
```js
import Headroom from "headroom.js";
// select your header or whatever element you wish
const header = document.querySelector("header");
const headroom = new Headroom(header);
headroom.init();
```
Then you can add the following CSS to your page:
```css
.headroom {
will-change: transform;
transition: transform 200ms linear;
}
.headroom--pinned {
transform: translateY(0%);
}
.headroom--unpinned {
transform: translateY(-100%);
}
```
You should now see your header slide in and out in response to the user's scroll.
## Contributions & Issues
Contributions are welcome. Please clearly explain the purpose of the PR and follow the current code style.
Issues can be resolved quickest if they are descriptive and include both a reduced test case and a set of steps to reproduce.
## Contributing Guide
### Setup
The following steps will get you setup to contribute changes to this repo:
1. Fork the repo (click the <kbd>Fork</kbd> button at the top right of [this page](https://github.com/WickyNilliams/headroom.js))
2. Clone your fork locally
```bash
git clone https://github.com/<your_github_username>/headroom.js.git
cd headroom.js
```
3. Install dependencies. This repo uses `npm`, so you should too.
```bash
npm install
```
### Building
To build the project:
```bash
npm run build
```
To start a watcher for building the project and running tests:
```bash
npm start
```
### Testing
To run the test suite in headless mode:
```bash
npm test
```
## License
Licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
================================================
FILE: cypress/.eslintrc
================================================
{
"extends": ["plugin:cypress/recommended"]
}
================================================
FILE: cypress/fixtures/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<script src="../../dist/headroom.js"></script>
<style>
html,
body {
margin: 0;
padding: 0;
}
body {
font-family: sans-serif;
}
header {
font-size: 1.5em;
background-color: rgba(0, 0, 0, 0.9);
color: white;
padding: 1em;
position: fixed;
width: 100%;
will-change: transform;
transition: transform 200ms ease-in;
}
.headroom--pinned {
transform: translateY(0);
}
.headroom--unpinned {
transform: translateY(-100%);
}
h1 {
font-size: 5em;
text-align: center;
background-color: coral;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
margin: 0;
height: 100vh;
padding-top: 2.5em;
}
main {
max-width: 40em;
}
.scroller {
background-color: rgba(0, 0, 0, 0.3);
height: 400px;
overflow-y: auto;
}
</style>
</head>
<body>
<header id="header">
hello world
</header>
<h1>Lorem ipsum</h1>
<main>
<p>
<strong>Pellentesque habitant morbi tristique</strong> senectus et netus
et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat
vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet
quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris
placerat eleifend leo. Quisque sit amet est et sapien ullamcorper
pharetra. Vestibulum erat wisi, condimentum sed,
<code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum,
elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus
lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar
facilisis. Ut felis.
</p>
<h2>Header Level 2</h2>
<ol>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ol>
<blockquote>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
magna. Cras in mi at felis aliquet congue. Ut a est eget ligula
molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis
mollis, tellus est malesuada tellus, at luctus turpis elit sit amet
quam. Vivamus pretium ornare est.
</p>
</blockquote>
<h3>Header Level 3</h3>
<ul>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ul>
<pre><code>
#header h1 a {
display: block;
width: 300px;
height: 80px;
}
</code></pre>
<p>
<strong>Pellentesque habitant morbi tristique</strong> senectus et netus
et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat
vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet
quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris
placerat eleifend leo. Quisque sit amet est et sapien ullamcorper
pharetra. Vestibulum erat wisi, condimentum sed,
<code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum,
elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus
lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar
facilisis. Ut felis.
</p>
<h2>Header Level 2</h2>
<ol>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ol>
<blockquote>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
magna. Cras in mi at felis aliquet congue. Ut a est eget ligula
molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis
mollis, tellus est malesuada tellus, at luctus turpis elit sit amet
quam. Vivamus pretium ornare est.
</p>
</blockquote>
<h3>Header Level 3</h3>
<ul>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ul>
<pre><code>
#header h1 a {
display: block;
width: 300px;
height: 80px;
}
</code></pre>
<p>
<strong>Pellentesque habitant morbi tristique</strong> senectus et netus
et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat
vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet
quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris
placerat eleifend leo. Quisque sit amet est et sapien ullamcorper
pharetra. Vestibulum erat wisi, condimentum sed,
<code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum,
elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus
lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar
facilisis. Ut felis.
</p>
<h2>Header Level 2</h2>
<ol>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ol>
<blockquote>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
magna. Cras in mi at felis aliquet congue. Ut a est eget ligula
molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis
mollis, tellus est malesuada tellus, at luctus turpis elit sit amet
quam. Vivamus pretium ornare est.
</p>
</blockquote>
<h3>Header Level 3</h3>
<ul>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ul>
<pre><code>
#header h1 a {
display: block;
width: 300px;
height: 80px;
}
</code></pre>
<p>
<strong>Pellentesque habitant morbi tristique</strong> senectus et netus
et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat
vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet
quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris
placerat eleifend leo. Quisque sit amet est et sapien ullamcorper
pharetra. Vestibulum erat wisi, condimentum sed,
<code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum,
elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus
lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar
facilisis. Ut felis.
</p>
<h2>Header Level 2</h2>
<ol>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ol>
<blockquote>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
magna. Cras in mi at felis aliquet congue. Ut a est eget ligula
molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis
mollis, tellus est malesuada tellus, at luctus turpis elit sit amet
quam. Vivamus pretium ornare est.
</p>
</blockquote>
<h3>Header Level 3</h3>
<ul>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ul>
<pre><code>
#header h1 a {
display: block;
width: 300px;
height: 80px;
}
</code></pre>
<div class="scroller">
<p>
<strong>Pellentesque habitant morbi tristique</strong> senectus et
netus et malesuada fames ac turpis egestas. Vestibulum tortor quam,
feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero
sit amet quam egestas semper.
<em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo.
Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat
wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet,
wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum
rutrum orci, sagittis tempus lacus enim ac dui.
<a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.
</p>
<h2>Header Level 2</h2>
<ol>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ol>
<blockquote>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
magna. Cras in mi at felis aliquet congue. Ut a est eget ligula
molestie gravida. Curabitur massa. Donec eleifend, libero at
sagittis mollis, tellus est malesuada tellus, at luctus turpis elit
sit amet quam. Vivamus pretium ornare est.
</p>
</blockquote>
<h3>Header Level 3</h3>
<ul>
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
<li>Aliquam tincidunt mauris eu risus.</li>
</ul>
<pre><code>
#header h1 a {
display: block;
width: 300px;
height: 80px;
}
</code></pre>
</div>
<iframe
srcdoc='<p> <strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p><h2>Header Level 2</h2><ol><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ol><blockquote><p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote><h3>Header Level 3</h3><ul><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ul>'
></iframe>
</main>
</body>
</html>
================================================
FILE: cypress/integration/headroom.spec.js
================================================
import createScroller from "../../src/scroller";
describe("Headroom", function() {
const initialiseHeadroom = options => {
cy.window().then(win => {
win.hr = new win.Headroom(win.document.querySelector("header"), options);
win.hr.init();
});
cy.wait(200); // eslint-disable-line cypress/no-unnecessary-waiting
};
beforeEach(() => {
cy.visit("./cypress/fixtures/index.html");
});
afterEach(() => {
cy.window().then(win => {
win.hr.destroy();
});
cy.get("header").should("be.destroyed");
});
it("works!", function() {
initialiseHeadroom();
cy.get("header")
.should("be.initialised")
.should("be.top")
.should("not.be.bottom");
cy.scrollTo(0, 50);
cy.get("header")
.should("not.be.pinned")
.should("not.be.top")
.should("not.be.bottom");
cy.scrollTo(0, 25);
cy.get("header")
.should("be.pinned")
.should("not.be.top")
.should("not.be.bottom");
cy.scrollTo(0, 0);
cy.get("header")
.should("be.pinned")
.should("be.top")
.should("not.be.bottom");
cy.window()
.then(win => createScroller(win.hr.scroller))
.then(scroller => {
const distanceToBottom = scroller.scrollHeight() - scroller.height();
cy.scrollTo(0, distanceToBottom - 1);
cy.get("header")
.should("not.be.pinned")
.should("not.be.top")
.should("not.be.bottom");
cy.scrollTo(0, distanceToBottom);
cy.get("header")
.should("not.be.pinned")
.should("not.be.top")
.should("be.bottom");
});
});
it("handles tolerance correctly", () => {
initialiseHeadroom({
tolerance: 10
});
cy.scrollTo(0, 5);
cy.get("header").should("be.initialised");
cy.scrollTo(0, 15);
cy.get("header").should("not.be.pinned");
cy.scrollTo(0, 12);
cy.get("header").should("not.be.pinned");
cy.scrollTo(0, 2);
cy.get("header").should("be.pinned");
});
it("handles offset correctly", () => {
initialiseHeadroom({
offset: 50,
tolerance: 10
});
cy.scrollTo(0, 25);
cy.get("header")
.should("be.initialised")
.should("be.top");
cy.scrollTo(0, 55);
cy.get("header")
.should("not.be.pinned")
.should("not.be.top");
cy.scrollTo(0, 49);
cy.get("header")
.should("be.pinned")
.should("be.top");
});
it("handles up/down offset correctly", () => {
initialiseHeadroom({
offset: {
up: 70,
down: 120,
}
});
cy.scrollTo(0, 119);
cy.get("header")
.should("be.initialised")
.should("be.top");
cy.scrollTo(0, 140);
cy.get("header")
.should("not.be.pinned")
.should("not.be.top");
cy.scrollTo(0, 121);
cy.get("header")
.should("be.pinned")
.should("not.be.top");
cy.scrollTo(0, 69);
cy.get("header")
.should("be.pinned")
.should("be.top");
});
it("can be frozen / unfrozen", () => {
initialiseHeadroom();
cy.scrollTo(0, 20);
cy.get("header").should("not.be.pinned");
cy.window().then(win => {
win.hr.freeze();
});
cy.scrollTo(0, 10);
cy.get("header")
.should("be.froze")
.should("not.be.pinned");
cy.window().then(win => {
win.hr.unfreeze();
});
cy.scrollTo(0, 5);
cy.get("header")
.should("not.be.froze")
.should("be.pinned");
});
it("handles scrollers besides window", () => {
cy.get(".scroller").then(scroller => {
initialiseHeadroom({ scroller: scroller[0] });
});
cy.get("header").should("be.initialised");
cy.get(".scroller").scrollTo(0, 50);
cy.get("header")
.should("not.be.pinned")
.should("not.be.top")
.should("not.be.bottom");
cy.get(".scroller").scrollTo(0, 25);
cy.get("header")
.should("be.pinned")
.should("not.be.top")
.should("not.be.bottom");
cy.get(".scroller").scrollTo(0, 0);
cy.get("header")
.should("be.pinned")
.should("be.top")
.should("not.be.bottom");
cy.window()
.then(win => createScroller(win.hr.scroller))
.then(scroller => {
const distanceToBottom = scroller.scrollHeight() - scroller.height();
cy.get(".scroller").scrollTo(0, distanceToBottom - 1);
cy.get("header")
.should("not.be.pinned")
.should("not.be.top")
.should("not.be.bottom");
cy.get(".scroller").scrollTo(0, distanceToBottom);
cy.get("header")
.should("not.be.pinned")
.should("not.be.top")
.should("be.bottom");
});
});
it("handles programmatically pinning/unpinning", () => {
initialiseHeadroom();
cy.window().then(win => {
win.hr.unpin();
});
cy.get("header").should("not.be.pinned");
cy.window().then(win => {
win.hr.pin();
});
cy.get("header").should("be.pinned");
});
it("handles an iframe's window as the scroll source", () => {
cy.get("iframe").then(([iframe]) => {
initialiseHeadroom({
scroller: iframe.contentWindow
});
});
cy.get("header").should("be.initialised");
cy.get("iframe").then(([iframe]) => {
iframe.contentWindow.scroll(0, 50);
});
cy.get("header")
.should("not.be.pinned")
.should("not.be.top")
.should("not.be.bottom");
cy.get("iframe").then(([iframe]) => {
iframe.contentWindow.scroll(0, 25);
});
cy.get("header")
.should("be.pinned")
.should("not.be.top")
.should("not.be.bottom");
});
it("fires callbacks", () => {
let pinStatus, topStatus, bottomStatus;
initialiseHeadroom({
onPin: () => {
pinStatus = "pinned";
},
onUnpin: () => {
pinStatus = "unpinned";
},
onTop: () => {
topStatus = "top";
},
onNotTop: () => {
topStatus = "notTop";
},
onBottom: () => {
bottomStatus = "bottom";
},
onNotBottom: () => {
bottomStatus = "notBottom";
}
});
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(0).should(() => {
expect(topStatus).to.equal("top");
expect(pinStatus).to.equal(undefined);
expect(bottomStatus).to.equal("notBottom");
});
cy.scrollTo(0, 50);
cy.should(() => {
expect(topStatus).to.equal("notTop");
expect(pinStatus).to.equal("unpinned");
expect(bottomStatus).to.equal("notBottom");
});
cy.scrollTo(0, 25);
cy.should(() => {
expect(topStatus).to.equal("notTop");
expect(pinStatus).to.equal("pinned");
expect(bottomStatus).to.equal("notBottom");
});
cy.scrollTo(0, 0);
cy.should(() => {
expect(topStatus).to.equal("top");
expect(pinStatus).to.equal("pinned");
expect(bottomStatus).to.equal("notBottom");
});
cy.scrollTo("bottom");
cy.should(() => {
expect(topStatus).to.equal("notTop");
expect(pinStatus).to.equal("unpinned");
expect(bottomStatus).to.equal("bottom");
});
});
describe("handling options and defaults", () => {
it("merges our own classes and preserves other defaults", () => {
const classes = {
initial: "foo",
pinned: "foo--pinned"
};
initialiseHeadroom({ classes });
cy.window().then(win => {
expect(win.hr.classes).to.deep.contain(classes);
const { initial, pinned, ...defaultClasses } = win.hr.classes; // eslint-disable-line no-unused-vars
expect(win.hr.classes).to.deep.contain(defaultClasses);
});
});
it("assigns default classes if no options supplied", () => {
initialiseHeadroom();
cy.window().then(win => {
expect(win.hr.classes).to.deep.equal(win.Headroom.options.classes);
});
});
it("assigns default classes if no no classes supplied", () => {
initialiseHeadroom({ tolerance: 5 });
cy.window().then(win => {
expect(win.hr.classes).to.deep.equal(win.Headroom.options.classes);
});
});
it("handles multiple classes", () => {
initialiseHeadroom({
classes: {
pinned: "headroom--pinned foo",
unpinned: "headroom--unpinned bar"
}
});
cy.scrollTo(0, 50);
cy.get("header")
.should("not.be.pinned")
.should("have.class", "bar");
cy.scrollTo(0, 25);
cy.get("header")
.should("be.pinned")
.should("have.class", "foo");
});
});
});
================================================
FILE: cypress/plugins/index.js
================================================
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
module.exports = (/*on, config*/) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
};
================================================
FILE: cypress/support/commands.js
================================================
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
================================================
FILE: cypress/support/index.js
================================================
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "./commands";
// Alternatively you can use CommonJS syntax:
// require('./commands')
chai.use((_chai, utils) => {
_chai.Assertion.addMethod("pinned", function assertIsPinned() {
const negate = utils.flag(this, "negate");
const obj = utils.flag(this, "object");
const assertion = new _chai.Assertion(obj);
if (negate) {
assertion.to.have
.class("headroom--unpinned")
.to.not.have.class("headroom--pinned");
} else {
assertion.to.have
.class("headroom--pinned")
.to.not.have.class("headroom--unpinned");
}
});
});
chai.use((_chai, utils) => {
_chai.Assertion.addMethod("top", function assertIsTop() {
const negate = utils.flag(this, "negate");
const obj = utils.flag(this, "object");
const assertion = new _chai.Assertion(obj);
if (negate) {
assertion.to.have
.class("headroom--not-top")
.to.not.have.class("headroom--top");
} else {
assertion.to.have
.class("headroom--top")
.to.not.have.class("headroom--not-top");
}
});
});
chai.use((_chai, utils) => {
_chai.Assertion.addMethod("bottom", function assertIsBottom() {
const negate = utils.flag(this, "negate");
const obj = utils.flag(this, "object");
const assertion = new _chai.Assertion(obj);
if (negate) {
assertion.to.have
.class("headroom--not-bottom")
.to.not.have.class("headroom--bottom");
} else {
assertion.to.have
.class("headroom--bottom")
.to.not.have.class("headroom--not-bottom");
}
});
});
chai.use((_chai, utils) => {
_chai.Assertion.addMethod("froze", function assertIsFrozen() {
const negate = utils.flag(this, "negate");
const obj = utils.flag(this, "object");
const assertion = new _chai.Assertion(obj);
if (negate) {
assertion.not.to.have.class("headroom--frozen");
} else {
assertion.to.have.class("headroom--frozen");
}
});
});
chai.use((_chai, utils) => {
_chai.Assertion.addMethod("destroyed", function assertIsDestroyed() {
const obj = utils.flag(this, "object");
const assertion = new _chai.Assertion(obj);
assertion.to.not.have
.class("headroom--pinned")
.to.not.have.class("headroom--unpinned")
.to.not.have.class("headroom--top")
.to.not.have.class("headroom--not-top")
.to.not.have.class("headroom--bottom")
.to.not.have.class("headroom--not-bottom");
});
});
chai.use((_chai, utils) => {
_chai.Assertion.addMethod("initialised", function assertIsInitialised() {
const obj = utils.flag(this, "object");
const assertion = new _chai.Assertion(obj);
assertion.to.have
.class("headroom")
.to.not.have.class("headroom--pinned")
.to.not.have.class("headroom--unpinned");
});
});
================================================
FILE: cypress.json
================================================
{
"video": false
}
================================================
FILE: package.json
================================================
{
"name": "headroom.js",
"version": "0.12.0",
"description": "Give your page some headroom. Hide your header until you need it",
"main": "dist/headroom.js",
"files": [
"dist"
],
"scripts": {
"start": "rollup --config --watch & cypress open",
"test": "npm run build && cypress run",
"build": "rollup --config",
"version": "npm run build",
"postversion": "git push origin master --tags && npm publish"
},
"repository": {
"type": "git",
"url": "https://github.com/WickyNilliams/headroom.js"
},
"keywords": [
"header",
"fixed",
"scroll",
"menu"
],
"author": "Nick Williams",
"homepage": "http://wicky.nillia.ms/headroom.js",
"license": "MIT",
"bugs": {
"url": "https://github.com/WickyNilliams/headroom.js/issues"
},
"devDependencies": {
"cypress": "^3.4.1",
"eslint": "^6.5.0",
"eslint-plugin-cypress": "^2.7.0",
"rollup": "^1.19.4",
"rollup-plugin-eslint": "^7.0.0",
"rollup-plugin-filesize": "^6.2.0",
"rollup-plugin-license": "^0.12.1",
"rollup-plugin-uglify": "^6.0.2"
}
}
================================================
FILE: rollup.config.js
================================================
import license from "rollup-plugin-license";
import { uglify } from "rollup-plugin-uglify";
import filesize from "rollup-plugin-filesize";
import { eslint } from "rollup-plugin-eslint";
const input = "src/Headroom.js";
const output = {
format: "umd",
name: "Headroom"
};
const licensePlugin = license({
banner: {
commentStyle: "ignored",
content: `<%= pkg.name %> v<%= pkg.version %> - <%= pkg.description %>
Copyright (c) <%= moment().format('YYYY') %> <%= pkg.author %> - <%= pkg.homepage %>
License: <%= pkg.license %>`
}
});
const unminified = {
input,
output: {
...output,
file: "dist/headroom.js"
},
plugins: [
eslint(),
licensePlugin,
filesize({
showMinifiedSize: false,
showGzippedSize: false
})
]
};
const minified = {
input,
output: {
...output,
file: "dist/headroom.min.js",
compact: true
},
plugins: [
uglify(),
licensePlugin,
filesize({
showMinifiedSize: false,
showBrotliSize: true
})
]
};
export default [unminified, minified];
================================================
FILE: src/.eslintrc
================================================
{
"parserOptions": {
"ecmaVersion": 6
},
"env": {
"browser": true
}
}
================================================
FILE: src/Headroom.js
================================================
import { isBrowser, isSupported } from "./features";
import trackScroll from "./trackScroll";
function normalizeUpDown(t) {
return t === Object(t) ? t : { down: t, up: t };
}
/**
* UI enhancement for fixed headers.
* Hides header when scrolling down
* Shows header when scrolling up
* @constructor
* @param {DOMElement} elem the header element
* @param {Object} options options for the widget
*/
function Headroom(elem, options) {
options = options || {};
Object.assign(this, Headroom.options, options);
this.classes = Object.assign({}, Headroom.options.classes, options.classes);
this.elem = elem;
this.tolerance = normalizeUpDown(this.tolerance);
this.offset = normalizeUpDown(this.offset);
this.initialised = false;
this.frozen = false;
}
Headroom.prototype = {
constructor: Headroom,
/**
* Start listening to scrolling
* @public
*/
init: function() {
if (Headroom.cutsTheMustard && !this.initialised) {
this.addClass("initial");
this.initialised = true;
// defer event registration to handle browser
// potentially restoring previous scroll position
setTimeout(
function(self) {
self.scrollTracker = trackScroll(
self.scroller,
{ offset: self.offset, tolerance: self.tolerance },
self.update.bind(self)
);
},
100,
this
);
}
return this;
},
/**
* Destroy the widget, clearing up after itself
* @public
*/
destroy: function() {
this.initialised = false;
Object.keys(this.classes).forEach(this.removeClass, this);
this.scrollTracker.destroy();
},
/**
* Unpin the element
* @public
*/
unpin: function() {
if (this.hasClass("pinned") || !this.hasClass("unpinned")) {
this.addClass("unpinned");
this.removeClass("pinned");
if (this.onUnpin) {
this.onUnpin.call(this);
}
}
},
/**
* Pin the element
* @public
*/
pin: function() {
if (this.hasClass("unpinned")) {
this.addClass("pinned");
this.removeClass("unpinned");
if (this.onPin) {
this.onPin.call(this);
}
}
},
/**
* Freezes the current state of the widget
* @public
*/
freeze: function() {
this.frozen = true;
this.addClass("frozen");
},
/**
* Re-enables the default behaviour of the widget
* @public
*/
unfreeze: function() {
this.frozen = false;
this.removeClass("frozen");
},
top: function() {
if (!this.hasClass("top")) {
this.addClass("top");
this.removeClass("notTop");
if (this.onTop) {
this.onTop.call(this);
}
}
},
notTop: function() {
if (!this.hasClass("notTop")) {
this.addClass("notTop");
this.removeClass("top");
if (this.onNotTop) {
this.onNotTop.call(this);
}
}
},
bottom: function() {
if (!this.hasClass("bottom")) {
this.addClass("bottom");
this.removeClass("notBottom");
if (this.onBottom) {
this.onBottom.call(this);
}
}
},
notBottom: function() {
if (!this.hasClass("notBottom")) {
this.addClass("notBottom");
this.removeClass("bottom");
if (this.onNotBottom) {
this.onNotBottom.call(this);
}
}
},
shouldUnpin: function(details) {
var scrollingDown = details.direction === "down";
return scrollingDown && !details.top && details.toleranceExceeded;
},
shouldPin: function(details) {
var scrollingUp = details.direction === "up";
return (scrollingUp && details.toleranceExceeded) || details.top;
},
addClass: function(className) {
this.elem.classList.add.apply(
this.elem.classList,
this.classes[className].split(" ")
);
},
removeClass: function(className) {
this.elem.classList.remove.apply(
this.elem.classList,
this.classes[className].split(" ")
);
},
hasClass: function(className) {
return this.classes[className].split(" ").every(function(cls) {
return this.classList.contains(cls);
}, this.elem);
},
update: function(details) {
if (details.isOutOfBounds) {
// Ignore bouncy scrolling in OSX
return;
}
if (this.frozen === true) {
return;
}
if (details.top) {
this.top();
} else {
this.notTop();
}
if (details.bottom) {
this.bottom();
} else {
this.notBottom();
}
if (this.shouldUnpin(details)) {
this.unpin();
} else if (this.shouldPin(details)) {
this.pin();
}
}
};
/**
* Default options
* @type {Object}
*/
Headroom.options = {
tolerance: {
up: 0,
down: 0
},
offset: 0,
scroller: isBrowser() ? window : null,
classes: {
frozen: "headroom--frozen",
pinned: "headroom--pinned",
unpinned: "headroom--unpinned",
top: "headroom--top",
notTop: "headroom--not-top",
bottom: "headroom--bottom",
notBottom: "headroom--not-bottom",
initial: "headroom"
}
};
Headroom.cutsTheMustard = isSupported();
export default Headroom;
================================================
FILE: src/angular.headroom.js
================================================
(function (angular, Headroom) {
if(!angular) {
return;
}
function headroom(HeadroomService) {
return {
scope: {
tolerance: '=',
offset: '=',
classes: '=',
scroller: '@'
},
link: function ($scope, $element) {
var options = {};
var opts = HeadroomService.options;
for (var prop in opts) {
options[prop] = $scope[prop] || opts[prop];
}
if ($scope.scroller) {
options.scroller = document.querySelector($scope.scroller);
}
var headroom = new HeadroomService($element[0], options).init();
$scope.$on('$destroy', function(){
headroom.destroy();
});
}
};
}
headroom.$inject = ['HeadroomService'];
function HeadroomService() {
return Headroom;
}
angular
.module('headroom', [])
.directive('headroom', headroom)
.factory('HeadroomService', HeadroomService);
})(window.angular, window.Headroom);
================================================
FILE: src/features.js
================================================
export function isBrowser() {
return typeof window !== "undefined";
}
/**
* Used to detect browser support for adding an event listener with options
* Credit: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
*/
export function passiveEventsSupported() {
var supported = false;
try {
var options = {
// eslint-disable-next-line getter-return
get passive() {
supported = true;
}
};
window.addEventListener("test", options, options);
window.removeEventListener("test", options, options);
} catch (err) {
supported = false;
}
return supported;
}
export function isSupported() {
return !!(
isBrowser() &&
function() {}.bind &&
"classList" in document.documentElement &&
Object.assign &&
Object.keys &&
requestAnimationFrame
);
}
================================================
FILE: src/jQuery.headroom.js
================================================
(function($) {
if(!$) {
return;
}
////////////
// Plugin //
////////////
$.fn.headroom = function(option) {
return this.each(function() {
var $this = $(this),
data = $this.data('headroom'),
options = typeof option === 'object' && option;
options = $.extend(true, {}, Headroom.options, options);
if (!data) {
data = new Headroom(this, options);
data.init();
$this.data('headroom', data);
}
if (typeof option === 'string') {
data[option]();
if(option === 'destroy'){
$this.removeData('headroom');
}
}
});
};
//////////////
// Data API //
//////////////
$('[data-headroom]').each(function() {
var $this = $(this);
$this.headroom($this.data());
});
}(window.Zepto || window.jQuery));
================================================
FILE: src/scroller.js
================================================
function isDocument(obj) {
return obj.nodeType === 9; // Node.DOCUMENT_NODE === 9
}
function isWindow(obj) {
// `obj === window` or `obj instanceof Window` is not sufficient,
// as the obj may be the window of an iframe.
return obj && obj.document && isDocument(obj.document);
}
function windowScroller(win) {
var doc = win.document;
var body = doc.body;
var html = doc.documentElement;
return {
/**
* @see http://james.padolsey.com/javascript/get-document-height-cross-browser/
* @return {Number} the scroll height of the document in pixels
*/
scrollHeight: function() {
return Math.max(
body.scrollHeight,
html.scrollHeight,
body.offsetHeight,
html.offsetHeight,
body.clientHeight,
html.clientHeight
);
},
/**
* @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript
* @return {Number} the height of the viewport in pixels
*/
height: function() {
return win.innerHeight || html.clientHeight || body.clientHeight;
},
/**
* Gets the Y scroll position
* @return {Number} pixels the page has scrolled along the Y-axis
*/
scrollY: function() {
if (win.pageYOffset !== undefined) {
return win.pageYOffset;
}
return (html || body.parentNode || body).scrollTop;
}
};
}
function elementScroller(element) {
return {
/**
* @return {Number} the scroll height of the element in pixels
*/
scrollHeight: function() {
return Math.max(
element.scrollHeight,
element.offsetHeight,
element.clientHeight
);
},
/**
* @return {Number} the height of the element in pixels
*/
height: function() {
return Math.max(element.offsetHeight, element.clientHeight);
},
/**
* Gets the Y scroll position
* @return {Number} pixels the element has scrolled along the Y-axis
*/
scrollY: function() {
return element.scrollTop;
}
};
}
export default function createScroller(element) {
return isWindow(element) ? windowScroller(element) : elementScroller(element);
}
================================================
FILE: src/trackScroll.js
================================================
import createScroller from "./scroller";
import { passiveEventsSupported } from "./features";
/**
* @param element EventTarget
*/
export default function trackScroll(element, options, callback) {
var isPassiveSupported = passiveEventsSupported();
var rafId;
var scrolled = false;
var scroller = createScroller(element);
var lastScrollY = scroller.scrollY();
var details = {};
function update() {
var scrollY = Math.round(scroller.scrollY());
var height = scroller.height();
var scrollHeight = scroller.scrollHeight();
// reuse object for less memory churn
details.scrollY = scrollY;
details.lastScrollY = lastScrollY;
details.direction = scrollY > lastScrollY ? "down" : "up";
details.distance = Math.abs(scrollY - lastScrollY);
details.isOutOfBounds = scrollY < 0 || scrollY + height > scrollHeight;
details.top = scrollY <= options.offset[details.direction];
details.bottom = scrollY + height >= scrollHeight;
details.toleranceExceeded =
details.distance > options.tolerance[details.direction];
callback(details);
lastScrollY = scrollY;
scrolled = false;
}
function handleScroll() {
if (!scrolled) {
scrolled = true;
rafId = requestAnimationFrame(update);
}
}
var eventOptions = isPassiveSupported
? { passive: true, capture: false }
: false;
element.addEventListener("scroll", handleScroll, eventOptions);
update();
return {
destroy: function() {
cancelAnimationFrame(rafId);
element.removeEventListener("scroll", handleScroll, eventOptions);
}
};
}
gitextract_56e1gft5/
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── cypress/
│ ├── .eslintrc
│ ├── fixtures/
│ │ └── index.html
│ ├── integration/
│ │ └── headroom.spec.js
│ ├── plugins/
│ │ └── index.js
│ └── support/
│ ├── commands.js
│ └── index.js
├── cypress.json
├── package.json
├── rollup.config.js
└── src/
├── .eslintrc
├── Headroom.js
├── angular.headroom.js
├── features.js
├── jQuery.headroom.js
├── scroller.js
└── trackScroll.js
SYMBOL INDEX (13 symbols across 5 files)
FILE: src/Headroom.js
function normalizeUpDown (line 4) | function normalizeUpDown(t) {
function Headroom (line 16) | function Headroom(elem, options) {
FILE: src/angular.headroom.js
function headroom (line 7) | function headroom(HeadroomService) {
function HeadroomService (line 34) | function HeadroomService() {
FILE: src/features.js
function isBrowser (line 1) | function isBrowser() {
function passiveEventsSupported (line 9) | function passiveEventsSupported() {
function isSupported (line 28) | function isSupported() {
FILE: src/scroller.js
function isDocument (line 1) | function isDocument(obj) {
function isWindow (line 5) | function isWindow(obj) {
function windowScroller (line 11) | function windowScroller(win) {
function elementScroller (line 54) | function elementScroller(element) {
function createScroller (line 84) | function createScroller(element) {
FILE: src/trackScroll.js
function trackScroll (line 7) | function trackScroll(element, options, callback) {
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (46K chars).
[
{
"path": ".eslintrc",
"chars": 659,
"preview": "{\n \"parserOptions\": {\n \"ecmaVersion\": 2018,\n \"sourceType\": \"module\"\n },\n \"env\": {\n \"browser\": true,\n \"nod"
},
{
"path": ".gitignore",
"chars": 26,
"preview": "node_modules\ndist/\n.vscode"
},
{
"path": ".travis.yml",
"chars": 392,
"preview": "language: node_js\nnode_js:\n - \"10\"\naddons:\n apt:\n packages:\n # Ubuntu 16+ does not install this dependency by "
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2013 Nick Williams\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 2417,
"preview": "# Headroom.js\n\nHeadroom.js is a lightweight, high-performance JS widget (with no dependencies) that allows you to react "
},
{
"path": "cypress/.eslintrc",
"chars": 48,
"preview": "{\n \"extends\": [\"plugin:cypress/recommended\"]\n}\n"
},
{
"path": "cypress/fixtures/index.html",
"chars": 10841,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "cypress/integration/headroom.spec.js",
"chars": 8615,
"preview": "import createScroller from \"../../src/scroller\";\n\ndescribe(\"Headroom\", function() {\n const initialiseHeadroom = options"
},
{
"path": "cypress/plugins/index.js",
"chars": 649,
"preview": "// ***********************************************************\n// This example plugins/index.js can be used to load plug"
},
{
"path": "cypress/support/commands.js",
"chars": 841,
"preview": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom"
},
{
"path": "cypress/support/index.js",
"chars": 3395,
"preview": "// ***********************************************************\n// This example support/index.js is processed and\n// load"
},
{
"path": "cypress.json",
"chars": 21,
"preview": "{\n \"video\": false\n}\n"
},
{
"path": "package.json",
"chars": 1096,
"preview": "{\n \"name\": \"headroom.js\",\n \"version\": \"0.12.0\",\n \"description\": \"Give your page some headroom. Hide your header until"
},
{
"path": "rollup.config.js",
"chars": 1089,
"preview": "import license from \"rollup-plugin-license\";\nimport { uglify } from \"rollup-plugin-uglify\";\nimport filesize from \"rollup"
},
{
"path": "src/.eslintrc",
"chars": 86,
"preview": "{\n \"parserOptions\": {\n \"ecmaVersion\": 6\n },\n \"env\": {\n \"browser\": true\n }\n}\n"
},
{
"path": "src/Headroom.js",
"chars": 5119,
"preview": "import { isBrowser, isSupported } from \"./features\";\nimport trackScroll from \"./trackScroll\";\n\nfunction normalizeUpDown("
},
{
"path": "src/angular.headroom.js",
"chars": 999,
"preview": "(function (angular, Headroom) {\n\n if(!angular) {\n return;\n }\n \n function headroom(HeadroomService) {\n return {"
},
{
"path": "src/features.js",
"chars": 846,
"preview": "export function isBrowser() {\n return typeof window !== \"undefined\";\n}\n\n/**\n * Used to detect browser support for addin"
},
{
"path": "src/jQuery.headroom.js",
"chars": 851,
"preview": "(function($) {\n\n if(!$) {\n return;\n }\n\n ////////////\n // Plugin //\n ////////////\n\n $.fn.headroom = function(opt"
},
{
"path": "src/scroller.js",
"chars": 2193,
"preview": "function isDocument(obj) {\n return obj.nodeType === 9; // Node.DOCUMENT_NODE === 9\n}\n\nfunction isWindow(obj) {\n // `ob"
},
{
"path": "src/trackScroll.js",
"chars": 1608,
"preview": "import createScroller from \"./scroller\";\nimport { passiveEventsSupported } from \"./features\";\n\n/**\n * @param element Eve"
}
]
About this extraction
This page contains the full source code of the WickyNilliams/headroom.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (41.9 KB), approximately 11.4k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.