affixing-header
Advanced tools
Comparing version 0.3.3 to 1.0.0-beta.1
@@ -1,124 +0,145 @@ | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('onscrolling')) : | ||
typeof define === 'function' && define.amd ? define(['onscrolling'], factory) : | ||
global.affixingHeader = factory(global.onscrolling) | ||
}(this, function (onscrolling) { 'use strict'; | ||
'use strict'; | ||
var scrollYPrev = 0, | ||
scrollY = 0, | ||
upScrollCount = 0, | ||
isNavAffixed = false, | ||
isNavTransitioning = false, | ||
documentDimensions = {}, | ||
headerDimensions = {}, | ||
header, | ||
resizeTimeoutId; | ||
function affixNavBar() { | ||
isNavAffixed = true; | ||
isNavTransitioning = false; | ||
header.style.position = 'fixed'; | ||
header.style.top = '0px'; | ||
headerDimensions.top = 0; | ||
} | ||
function unAffixNavBar() { | ||
var newHeaderTop = false; | ||
if (!isNavAffixed) { | ||
// Nothing to do here | ||
return; | ||
} | ||
upScrollCount = 0; | ||
isNavAffixed = false; | ||
// Only set top position for switch from fixed absolute if not transitioning | ||
if (!isNavTransitioning) { | ||
// If user jumped down the page (e.g. paging with spacebar) | ||
if (scrollY > scrollYPrev + headerDimensions.height + 5) { | ||
newHeaderTop = scrollYPrev + 5; | ||
} else { | ||
newHeaderTop = scrollY; | ||
} | ||
} else { | ||
isNavTransitioning = false; | ||
} | ||
if (newHeaderTop !== false) { | ||
header.style.top = newHeaderTop + 'px'; | ||
headerDimensions.top = newHeaderTop; | ||
} | ||
header.style.position = 'absolute'; | ||
} | ||
function checkNavPosition() { | ||
if (!isNavAffixed && headerDimensions.top > scrollY) { | ||
affixNavBar(); | ||
} | ||
} | ||
function handleScroll(scrollYCurrent) { | ||
scrollY = scrollYCurrent; | ||
// Make sure that the nav bar doesn't wind up stranded in the middle of the page | ||
checkNavPosition(); | ||
// If this is bounce scrolling (e.g. Mac OS, iOS), bail | ||
// Another way to check the top | ||
//(scrollY + window.innerHeight) > document.documentElement.scrollHeight | ||
if (scrollY < 0 || documentDimensions.scrollHeight - documentDimensions.scrollTop < documentDimensions.clientHeight) { | ||
return; | ||
} | ||
if (scrollY < scrollYPrev) { | ||
// If the user has scrolled up quickly / jumped up (like shift-spacebar) | ||
// Or we are transitioning and have reached the top of the bar | ||
if ((!isNavAffixed && scrollY + headerDimensions.height + 10 < scrollYPrev) || isNavTransitioning && scrollY <= headerDimensions.top + 2) { | ||
affixNavBar(); | ||
} else if (!isNavAffixed && !isNavTransitioning) { | ||
if (upScrollCount > 6) { | ||
isNavAffixed = true; | ||
// Need header height, so update cached dimensions | ||
headerDimensions.height = header.offsetHeight; | ||
// If the navbar is not currently visible, set the top to just above the viewport so it appears as we scroll up | ||
if (scrollY > headerDimensions.top + headerDimensions.height + 25) { | ||
headerDimensions.top = scrollY - headerDimensions.height - 25; | ||
header.style.top = headerDimensions.top + 'px'; | ||
} | ||
isNavTransitioning = true; | ||
} | ||
upScrollCount++; | ||
} | ||
} else if (isNavAffixed) { | ||
unAffixNavBar(); | ||
} | ||
scrollYPrev = scrollY; | ||
} | ||
function calculateDimensions() { | ||
documentDimensions.clientHeight = document.documentElement.clientHeight; | ||
documentDimensions.scrollHeight = document.documentElement.scrollHeight; | ||
documentDimensions.scrollTop = document.documentElement.scrollTop; | ||
headerDimensions.height = header.offsetHeight; | ||
} | ||
function onResizeDebouncer() { | ||
if (resizeTimeoutId) { | ||
window.clearTimeout(resizeTimeoutId); | ||
} | ||
window.setTimeout(calculateDimensions, 150); | ||
} | ||
return function(navElement) { | ||
if (!navElement) { | ||
return; | ||
} | ||
// Set initial state | ||
header = navElement; | ||
header.style.position = 'absolute'; | ||
header.style.top = '0px'; | ||
headerDimensions.top = 0; | ||
// Trigger calculations caching and attach debouncer to resize event | ||
calculateDimensions(); | ||
window.addEventListener('resize', onResizeDebouncer); | ||
// Use onscrolling helper to listen for scroll changes | ||
onscrolling(handleScroll); | ||
}; | ||
})); | ||
import onscrolling from 'onscrolling'; | ||
// Keep track of: | ||
// - state of nav bar | ||
// - scrolling direction | ||
// - “deliberateness” of scroll in current direction (for affixing header, it shouldn't be just a casual slip) | ||
// - state when transitioning for adjusting position | ||
let scrollYPrev = 0; | ||
let scrollY = 0; | ||
let upScrollCount = 0; | ||
let isNavAffixed = false; | ||
let isNavTransitioning = false; | ||
let resizeTimeoutID = null; | ||
let header = null; | ||
let headerDimensions = {}; | ||
const documentDimensions = {}; | ||
function affixNavBar() { | ||
if (!header) | ||
return; | ||
isNavAffixed = true; | ||
isNavTransitioning = false; | ||
header.style.position = 'fixed'; | ||
header.style.top = '0px'; | ||
headerDimensions.top = 0; | ||
} | ||
function unaffixNavBar() { | ||
if (!isNavAffixed || !header || headerDimensions.height == null) { | ||
// Nothing to do here | ||
return; | ||
} | ||
let newHeaderTop = null; | ||
upScrollCount = 0; | ||
isNavAffixed = false; | ||
// Only set top position for switch from fixed absolute if not transitioning | ||
if (!isNavTransitioning) { | ||
// If user jumped down the page (e.g. paging with spacebar) | ||
if (scrollY > scrollYPrev + headerDimensions.height + 5) { | ||
newHeaderTop = scrollYPrev + 5; | ||
} | ||
else { | ||
newHeaderTop = scrollY; | ||
} | ||
} | ||
else { | ||
isNavTransitioning = false; | ||
} | ||
if (newHeaderTop != null) { | ||
header.style.top = newHeaderTop + 'px'; | ||
headerDimensions.top = newHeaderTop; | ||
} | ||
header.style.position = 'absolute'; | ||
} | ||
function checkNavPosition() { | ||
if (!isNavAffixed && | ||
headerDimensions.top != null && | ||
headerDimensions.top > scrollY) { | ||
affixNavBar(); | ||
} | ||
} | ||
function handleScroll({ scrollY: scrollYCurrent, }) { | ||
scrollY = scrollYCurrent; | ||
// Make sure that the nav bar doesn't wind up stranded in the middle of the page | ||
checkNavPosition(); | ||
// Type guard for null values | ||
if (header == null || | ||
documentDimensions.clientHeight == null || | ||
documentDimensions.scrollHeight == null || | ||
documentDimensions.scrollTop == null || | ||
headerDimensions.height == null || | ||
headerDimensions.top == null) { | ||
return; | ||
} | ||
// If this is bounce scrolling (e.g. Mac OS, iOS), bail | ||
// Another way to check the top | ||
//(scrollY + window.innerHeight) > document.documentElement.scrollHeight | ||
if (scrollY < 0 || | ||
documentDimensions.scrollHeight - documentDimensions.scrollTop < | ||
documentDimensions.clientHeight) { | ||
return; | ||
} | ||
if (scrollY < scrollYPrev) { | ||
// If the user has scrolled up quickly / jumped up (like shift-spacebar) | ||
// Or we are transitioning and have reached the top of the bar | ||
if ((!isNavAffixed && | ||
scrollY + headerDimensions.height + 10 < scrollYPrev) || | ||
(isNavTransitioning && scrollY <= headerDimensions.top + 2)) { | ||
affixNavBar(); | ||
} | ||
else if (!isNavAffixed && !isNavTransitioning) { | ||
if (upScrollCount > 6) { | ||
isNavAffixed = true; | ||
// Need header height, so update cached dimensions | ||
headerDimensions.height = header.offsetHeight; | ||
// If the navbar is not currently visible, set the top to just above the viewport so it appears as we scroll up | ||
if (scrollY > headerDimensions.top + headerDimensions.height + 25) { | ||
headerDimensions.top = scrollY - headerDimensions.height - 25; | ||
header.style.top = headerDimensions.top + 'px'; | ||
} | ||
isNavTransitioning = true; | ||
} | ||
upScrollCount++; | ||
} | ||
} | ||
else if (isNavAffixed) { | ||
unaffixNavBar(); | ||
} | ||
scrollYPrev = scrollY; | ||
} | ||
function calculateDimensions() { | ||
resizeTimeoutID = null; | ||
documentDimensions.clientHeight = document.documentElement.clientHeight; | ||
documentDimensions.scrollHeight = document.documentElement.scrollHeight; | ||
documentDimensions.scrollTop = document.documentElement.scrollTop; | ||
headerDimensions.height = header === null || header === void 0 ? void 0 : header.offsetHeight; | ||
} | ||
function onResizeDebouncer() { | ||
if (resizeTimeoutID != null) { | ||
window.clearTimeout(resizeTimeoutID); | ||
} | ||
resizeTimeoutID = window.setTimeout(calculateDimensions, 150); | ||
} | ||
export default function (navElement) { | ||
// Set initial state | ||
header = navElement; | ||
const initialHeaderPosition = header.style.position; | ||
const initialHeaderTop = header.style.top; | ||
header.style.position = 'absolute'; | ||
header.style.top = '0px'; | ||
headerDimensions.top = 0; | ||
// Trigger calculations caching and attach debouncer to resize event | ||
calculateDimensions(); | ||
window.addEventListener('resize', onResizeDebouncer); | ||
// Use onscrolling helper to listen for scroll changes | ||
const cleanupOnscrolling = onscrolling(handleScroll, { vertical: true }); | ||
return () => { | ||
cleanupOnscrolling(); | ||
if (header) { | ||
header.style.position = initialHeaderPosition; | ||
header.style.top = initialHeaderTop; | ||
header = null; | ||
} | ||
headerDimensions = {}; | ||
window.removeEventListener('resize', onResizeDebouncer); | ||
}; | ||
} | ||
//# sourceMappingURL=affixing-header.js.map |
138
package.json
{ | ||
"name": "affixing-header", | ||
"version": "0.3.3", | ||
"description": "An affixing header that behaves normally as a user navigates down a page, but reveals itself naturally when a user scrolls or drags upwards. Inspired by iOS Safari, Medium, and others.", | ||
"main": "dist/affixing-header.js", | ||
"jsnext:main": "src/affixing-header.js", | ||
"scripts": { | ||
"test": "npm run build && npm run lint:tests && node test/bootstrap.js", | ||
"test:mocha": "mocha --reporter spec test/bootstrap.js", | ||
"lint:src": "jshint src/affixing-header.js", | ||
"lint:tests": "jshint test/*.js && jshint --extract=auto test/*.html", | ||
"build": "npm run lint:src && npm run build:normal && npm run build:bundle && npm run build:minify", | ||
"build:normal": "esperanto --type umd --name affixingHeader --basedir=node_modules/onscrolling/src/ -i src/affixing-header.js -o dist/affixing-header.js", | ||
"build:bundle": "esperanto --bundle --type umd --name affixingHeader --basedir=node_modules/onscrolling/src/ -i src/affixing-header.js -o dist/affixing-header-bundled.js", | ||
"build:minify": "uglifyjs dist/affixing-header-bundled.js --screw-ie8 --mangle -o dist/affixing-header-bundled-min.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/acusti/affixing-header.git" | ||
}, | ||
"keywords": [ | ||
"scroll", | ||
"header", | ||
"navbar", | ||
"menubar", | ||
"fixed", | ||
"affix", | ||
"browser", | ||
"show-hide" | ||
], | ||
"author": "Andrew Patton <andrew@acusti.ca> (http://www.acusti.ca)", | ||
"license": "CC0", | ||
"bugs": { | ||
"url": "https://github.com/acusti/affixing-header/issues" | ||
}, | ||
"homepage": "https://github.com/acusti/affixing-header", | ||
"devDependencies": { | ||
"babel": "^4.7.16", | ||
"chai": "^2.2.0", | ||
"chai-as-promised": "^4.3.0", | ||
"colors": "^1.0.3", | ||
"es6-module-loader": "^0.16.1", | ||
"esperanto": "^0.6.24", | ||
"extend": "^2.0.0", | ||
"finalhandler": "^0.3.4", | ||
"jshint": "^2.6.3", | ||
"mocha": "^2.2.1", | ||
"mocha-multi": "^0.6.0", | ||
"mocha-sauce-notifying-reporter": "0.0.1", | ||
"saucelabs": "^0.1.1", | ||
"selenium-webdriver": "^2.45.1", | ||
"serve-static": "^1.9.2", | ||
"uglify-js": "^2.4.19" | ||
}, | ||
"dependencies": { | ||
"onscrolling": "^0.3.0" | ||
}, | ||
"jspm": { | ||
"directories": { | ||
"lib": "src" | ||
"name": "affixing-header", | ||
"version": "1.0.0-beta.1", | ||
"description": "An affixing header that behaves normally as a user navigates down a page, but reveals itself naturally when a user scrolls or drags upwards. Inspired by iOS Safari, Medium, and others.", | ||
"type": "module", | ||
"sideEffects": false, | ||
"exports": "./dist/affixing-header.js", | ||
"main": "./dist/affixing-header.js", | ||
"types": "./dist/affixing-header.d.ts", | ||
"files": ["dist"], | ||
"scripts": { | ||
"build": "npm run lint && tsc --build", | ||
"format": "prettier --write .", | ||
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .", | ||
"test": "vitest", | ||
"typecheck": "tsc" | ||
}, | ||
"main": "affixing-header", | ||
"format": "es6" | ||
} | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/acusti/affixing-header.git" | ||
}, | ||
"prettier": { | ||
"arrowParens": "always", | ||
"singleQuote": true, | ||
"printWidth": 84, | ||
"tabWidth": 4, | ||
"trailingComma": "all", | ||
"useTabs": false, | ||
"overrides": [ | ||
{ | ||
"files": "*.css", | ||
"options": { | ||
"singleQuote": false | ||
} | ||
} | ||
] | ||
}, | ||
"keywords": [ | ||
"scroll", | ||
"header", | ||
"navbar", | ||
"menubar", | ||
"fixed", | ||
"affix", | ||
"browser", | ||
"show-hide" | ||
], | ||
"author": "Andrew Patton <andrew@acusti.ca> (http://www.acusti.ca)", | ||
"license": "CC0-1.0", | ||
"bugs": { | ||
"url": "https://github.com/acusti/affixing-header/issues" | ||
}, | ||
"homepage": "https://github.com/acusti/affixing-header", | ||
"devDependencies": { | ||
"@testing-library/jest-dom": "^6.4.6", | ||
"@typescript-eslint/eslint-plugin": "^7.13.0", | ||
"@typescript-eslint/parser": "^7.13.0", | ||
"@vitest/coverage-v8": "^1.6.0", | ||
"eslint": "^8", | ||
"eslint-config-prettier": "^9.1.0", | ||
"eslint-import-resolver-typescript": "^3.6.1", | ||
"eslint-plugin-cypress": "^3.3.0", | ||
"eslint-plugin-deprecation": "^3.0.0", | ||
"eslint-plugin-import": "^2.29.1", | ||
"eslint-plugin-jest": "^28.6.0", | ||
"eslint-plugin-jest-dom": "^5.4.0", | ||
"eslint-plugin-markdown": "^5.0.0", | ||
"eslint-plugin-testing-library": "^6.2.2", | ||
"eslint-plugin-typescript-sort-keys": "^3.2.0", | ||
"happy-dom": "^14.12.0", | ||
"prettier": "^2.8.0", | ||
"typescript": "^5.3.3", | ||
"vite-tsconfig-paths": "^4.3.2", | ||
"vitest": "^1.6.0" | ||
}, | ||
"dependencies": { | ||
"onscrolling": "^2.0.0-beta.5" | ||
} | ||
} |
@@ -1,24 +0,21 @@ | ||
# Affixing Header [![Build Status](https://travis-ci.org/acusti/affixing-header.svg?branch=master)](https://travis-ci.org/acusti/affixing-header) | ||
# Affixing Header [![build workflow](https://github.com/acusti/affixing-header/actions/workflows/deploy.yml/badge.svg)](https://github.com/acusti/affixing-header/actions) | ||
Create an affixing header that behaves normally as a user navigates down a page, but reveals itself naturally when a user scrolls or drags upwards. Inspired by iOS Safari, Medium, and others. See [an example implementation][acusti.ca] to see what it’s about. Works on desktop and mobile browsers. | ||
[![Sauce Test Status](https://saucelabs.com/browser-matrix/acusti.svg)](https://saucelabs.com/u/acusti) | ||
## Usage | ||
The module itself is available in a wide range of flavors: | ||
The module is ESM-only and exports a single default `affixingHeader` function: | ||
1. As a CommonJS (Browserify-friendly) module (via [UMD]): `dist/affixing-header.js` | ||
2. As an AMD (RequireJS-friendly) module (also via UMD): `dist/affixing-header.js` | ||
3. As the `window.affixingHeader` global with dependencies bundled in: `dist/affixing-header-bundled.js`, or minified as `dist/affixing-header-bundled-min.js` | ||
4. As an ES6/ES2015 module (identified by `jsnext:main` in package.json), compatible with ES6-compatible module loaders like [SystemJS][] or compilers like [Babel][]: `src/affixing-header.js` | ||
### `affixingHeader( element )` | ||
Exports a single function via `require('affixing-header')` if being used with a CommonJS or AMD module loader, or else exposes the function as a global named `window.affixingHeader`. | ||
#### `element` HTMLElement | ||
### `affixingHeader( element )` | ||
The DOM element to which the affixing behavior should be attached. Must be a single `HTMLElement` (e.g., the result of `document.querySelector` or `document.getElementById`), not a `NodeList`. | ||
#### `element` Element | ||
### affixingHeader return value | ||
The DOM element to which the affixing behavior should be attached. Must be a single Element (e.g., the result of `document.querySelector` or `document.getElementById`), not a `NodeList`. | ||
#### `affixingHeader` function `(element: HTMLElement) => () => void` | ||
The `affixingHeader` function returns a cleanup function that takes no arguments and is used to remove listeners and cleanup the affixing behavior. | ||
### Dependencies | ||
@@ -30,10 +27,7 @@ | ||
The scroll handling uses `requestAnimationFrame`, which is [only available in IE10+][raf-caniuse]. For older browsers, your header won’t affix to the top of the page when you scroll up, but you shouldn’t see any other issues (yay progressive enhancement). To add full support for older browsers, just include a [requestAnimationFrame polyfill][raf-polyfill]. | ||
The scroll handling uses `requestAnimationFrame`, which is [only available in IE10+][raf-caniuse]. To add full support for older browsers, just include a [requestAnimationFrame polyfill][raf-polyfill]. | ||
[acusti.ca]: http://www.acusti.ca | ||
[UMD]: https://github.com/umdjs/umd | ||
[SystemJS]: https://github.com/systemjs/systemjs | ||
[Babel]: https://babeljs.io | ||
[onscrolling]: https://github.com/acusti/onscrolling | ||
[raf-caniuse]: http://caniuse.com/#feat=requestanimationframe | ||
[raf-polyfill]: https://gist.github.com/paulirish/1579671 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Misc. License Issues
License(Experimental) A package's licensing information has fine-grained problems.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 6 instances in 1 package
0
0
Yes
25291
20
12
198
33
+ Addedonscrolling@2.0.0-beta.7(transitive)
- Removedonscrolling@0.3.3(transitive)
Updatedonscrolling@^2.0.0-beta.5