Initial commit
This commit is contained in:
commit
f3781775ff
47
CONTRIBUTING.md
Normal file
47
CONTRIBUTING.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Contributing to this project
|
||||
|
||||
Please take a moment to review this document in order to make the contribution
|
||||
process easy and effective for everyone involved.
|
||||
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
The issue tracker is the preferred channel for [bug reports](#bugs) and
|
||||
[features requests](#features), but please respect the following restrictions:
|
||||
|
||||
* Please **do not** use the issue tracker for personal support requests.
|
||||
|
||||
* Please keep the discussion **on topic** and respect the opinions of others.
|
||||
|
||||
|
||||
<a name="bugs"></a>
|
||||
## Bug reports
|
||||
|
||||
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||
Good bug reports are extremely helpful - thank you!
|
||||
|
||||
Guidelines for bug reports:
|
||||
|
||||
1. **Use the GitHub issue search** — check if the issue has already been
|
||||
reported.
|
||||
|
||||
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||
latest branch in the repository.
|
||||
|
||||
3. **Isolate the problem** — create a [reduced test
|
||||
case](http://css-tricks.com/reduced-test-cases/) and a live example.
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report. What is
|
||||
your environment? What steps will reproduce the issue? What browser(s) and OS
|
||||
experience the problem? What would you expect to be the outcome? All these
|
||||
details will help people to fix any potential bugs.
|
||||
|
||||
|
||||
<a name="features"></a>
|
||||
## Feature requests
|
||||
|
||||
Feature requests are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature. Please
|
||||
provide as much detail and context as possible.
|
||||
2
LICENSE.txt
Normal file
2
LICENSE.txt
Normal file
@ -0,0 +1,2 @@
|
||||
This work is licensed under the Creative Commons Attribution 4.0 International License.
|
||||
To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
|
||||
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
||||
mhead.js
|
||||
================
|
||||
|
||||
A small framework for creating a beautiful mobile navigation header to accompany the mmenu.js navigation menu on your website and webapp.
|
||||
|
||||
Need help? Have a look at [the documentation](https://mmenujs.com/mhead-plugin/) for demos and documentation.
|
||||
|
||||
### Licence
|
||||
The mhead javascript plugin is licensed under the [CC-BY-4.0 license](http://creativecommons.org/licenses/by/4.0/).
|
||||
|
||||
### Development
|
||||
This project uses [Gulp](http://gulpjs.com/) to transpile, minify and concatenate the TS/JS and SCSS/CSS files.
|
||||
If you are unfamiliar with Gulp, check [this tutorial](https://travismaynard.com/writing/getting-started-with-gulp) on how to get started.<br />
|
||||
Run `gulp watch` in the command-line to put a watch on the files and run all scripts immediately after saving your changes.
|
||||
15
composer.json
Normal file
15
composer.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name" : "mhead-js",
|
||||
"version" : "2.0.0",
|
||||
"authors" : "Fred Heusschen <info@frebsite.nl>",
|
||||
"description" : "A small framework for creating a beautiful mobile navigation header to accompany the mmenu.js menu on your website and webapp.",
|
||||
"keywords" : [
|
||||
"app",
|
||||
"mobile",
|
||||
"navigation",
|
||||
"head",
|
||||
"header",
|
||||
"navbar"
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
}
|
||||
96
demo/css/demo.css
Normal file
96
demo/css/demo.css
Normal file
@ -0,0 +1,96 @@
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
background-color: #fff;
|
||||
font-family: Arial, Helvetica, Verdana;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #666;
|
||||
position: relative;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
body * {
|
||||
text-shadow: none;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
line-height: 1.25;
|
||||
font-weight: bold;
|
||||
margin: 20px 0 10px 0;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
h4, h5, h6 {
|
||||
font-size: 16px;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
a, a:link, a:active, a:visited, a:hover {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
nav:not(.mm-menu) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background: #4bb5ef;
|
||||
}
|
||||
|
||||
.header a {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 10px;
|
||||
transform: translate( 0, -50% );
|
||||
text-decoration: none;
|
||||
}
|
||||
.header span {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
.header input {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
height: 30px;
|
||||
width: calc(100% - 30px);
|
||||
padding: 0 10px;
|
||||
margin: 0 0 0 30px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
font-size: inherit;
|
||||
}
|
||||
.header button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 20px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
transform: translate( 0, -50% );
|
||||
border: none;
|
||||
background: none;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#page {
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
padding: 30vh 50px;
|
||||
text-align: center;
|
||||
}
|
||||
75
demo/css/site.css
Normal file
75
demo/css/site.css
Normal file
@ -0,0 +1,75 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: #3ea7e1;
|
||||
-webkit-text-size-adjust: none;
|
||||
font-family: Arial, Helvetica, Verdana;
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
h1 {
|
||||
text-shadow: 8px 10px 1px rgba(0,0,0,.1);
|
||||
text-transform: lowercase;
|
||||
font-family: 'Pacifico', Arial, sans-serif;
|
||||
font-weight: normal;
|
||||
font-size: 150px;
|
||||
line-height: 150px;
|
||||
letter-spacing: -10px;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
a,
|
||||
a:hover
|
||||
{
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.phone {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 50%;
|
||||
height: 760px;
|
||||
width: 430px;
|
||||
margin-top: -380px;
|
||||
background: url( ../img/iphonex-example-blue.png ) center top / 395px auto no-repeat transparent;
|
||||
}
|
||||
|
||||
.phone:before {
|
||||
content: '';
|
||||
border-radius: 30px 30px 0 0;
|
||||
background: url( ../img/iphonex-example-camera.png ) center top no-repeat #4bb5ef;
|
||||
display: block;
|
||||
width: 290px;
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
top: 62px;
|
||||
left: calc( 50% - 290px / 2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.phone iframe {
|
||||
position: absolute;
|
||||
top: 87px;
|
||||
left: 70px;
|
||||
z-index: 0;
|
||||
width: 290px;
|
||||
height: 600px;
|
||||
border: none;
|
||||
border-radius: 0 0 30px 30px;
|
||||
}
|
||||
|
||||
#page {
|
||||
width: 350px;
|
||||
position: fixed;
|
||||
margin-top: -20px;
|
||||
margin-left: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
75
demo/default.html
Normal file
75
demo/default.html
Normal file
@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="author" content="mmenujs.com" />
|
||||
<meta content="width=600px user-scalable=yes" name="viewport" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
|
||||
<title>mhead.js demo</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/demo.css">
|
||||
<link type="text/css" rel="stylesheet" href="http://fonts.googleapis.com/css?family=Pacifico" />
|
||||
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="../dist/mhead.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="page">
|
||||
|
||||
<div class="header" style="padding-bottom: 0px;">
|
||||
<span>demo</span>
|
||||
</div>
|
||||
|
||||
<form class="header sticky">
|
||||
<a class="fa fa-bars" href="#/menu"></a>
|
||||
<input placeholder="search" />
|
||||
<button type="submit" class="fa fa-search"></button>
|
||||
</form>
|
||||
|
||||
<div class="content">
|
||||
<p><strong>This is a demo.</strong><br />
|
||||
Scroll down to see the header in action.</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p><strong>Scroll a bit faster.</strong><br />
|
||||
If the header did not yet disappear.</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p><strong>Now scroll back up.</strong><br />
|
||||
To make the header appear again.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../dist/mhead.js"></script>
|
||||
<script>
|
||||
|
||||
// Create the sticky header.
|
||||
new Mhead( '.header.sticky', {
|
||||
scroll : {
|
||||
hide: 200
|
||||
},
|
||||
// hooks: {
|
||||
// 'scrolledIn': function () {
|
||||
// console.log('scrolledIn');
|
||||
// },
|
||||
// 'scrolledOut': function () {
|
||||
// console.log('scrolledOut');
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
|
||||
// For the demo.
|
||||
document.addEventListener('click', ( evnt ) => {
|
||||
if (evnt.target.closest('a[href^="#/"]')) {
|
||||
evnt.preventDefault();
|
||||
alert( 'Thank you for clicking, but that\'s a demo link.' );
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
demo/img/iphonex-example-blue.png
Normal file
BIN
demo/img/iphonex-example-blue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
demo/img/iphonex-example-camera.png
Normal file
BIN
demo/img/iphonex-example-camera.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
40
dist/_helpers.js
vendored
Normal file
40
dist/_helpers.js
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Deep extend an object with the given defaults.
|
||||
* Note that the extended object is not a clone, meaning the original object will also be updated.
|
||||
*
|
||||
* @param {object} orignl The object to extend to.
|
||||
* @param {object} dfault The object to extend from.
|
||||
* @return {object} The extended "orignl" object.
|
||||
*/
|
||||
export function extend(orignl, dfault) {
|
||||
if (type(orignl) != 'object') {
|
||||
orignl = {};
|
||||
}
|
||||
if (type(dfault) != 'object') {
|
||||
dfault = {};
|
||||
}
|
||||
for (let k in dfault) {
|
||||
if (!dfault.hasOwnProperty(k)) {
|
||||
continue;
|
||||
}
|
||||
if (typeof orignl[k] == 'undefined') {
|
||||
orignl[k] = dfault[k];
|
||||
}
|
||||
else if (type(orignl[k]) == 'object') {
|
||||
extend(orignl[k], dfault[k]);
|
||||
}
|
||||
}
|
||||
return orignl;
|
||||
}
|
||||
/**
|
||||
* Get the type of any given variable. Improvement of "typeof".
|
||||
*
|
||||
* @param {any} variable The variable.
|
||||
* @return {string} The type of the variable in lowercase.
|
||||
*/
|
||||
export function type(variable) {
|
||||
return {}.toString
|
||||
.call(variable)
|
||||
.match(/\s([a-zA-Z]+)/)[1]
|
||||
.toLowerCase();
|
||||
}
|
||||
1
dist/_version.js
vendored
Normal file
1
dist/_version.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default '2.0.0';
|
||||
9
dist/core/_options.js
vendored
Normal file
9
dist/core/_options.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
const options = {
|
||||
hooks: {},
|
||||
scroll: {
|
||||
pin: 0,
|
||||
unpin: 0,
|
||||
tolerance: 5
|
||||
}
|
||||
};
|
||||
export default options;
|
||||
138
dist/core/mhead.core.js
vendored
Normal file
138
dist/core/mhead.core.js
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
import options from './_options';
|
||||
import version from '../_version';
|
||||
/**
|
||||
* Class for a sticky navigational header.
|
||||
*/
|
||||
export default class Mhead {
|
||||
/**
|
||||
* Create a sticky header.
|
||||
* @param {HTMLElement|string} header The header node.
|
||||
* @param {object} [options=Mhead.options] Options for the header.
|
||||
*/
|
||||
constructor(header, options = Mhead.options) {
|
||||
// Get header node from string or element.
|
||||
this.header =
|
||||
typeof header == 'string' ? document.querySelector(header) : header;
|
||||
// Stop if there is no header element found.
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
// Extend options from defaults.
|
||||
this.opts = Object.assign(options, Mhead.options);
|
||||
this.initHooks();
|
||||
this.initScroll();
|
||||
}
|
||||
/**
|
||||
* Initiate the scroll functionality.
|
||||
*/
|
||||
initScroll() {
|
||||
if (!this.opts.scroll || this.opts.scroll.unpin === false) {
|
||||
return;
|
||||
}
|
||||
this.header.classList.add('mh-sticky');
|
||||
/** Minimum scroll position to unpin / hide the header. */
|
||||
var _min = this.header.offsetHeight * 2;
|
||||
this.opts.scroll.unpin = Math.max(_min, this.opts.scroll.unpin || 0);
|
||||
this.opts.scroll.pin = Math.max(_min, this.opts.scroll.pin || 0);
|
||||
this.state = null;
|
||||
/** Previous scroll position. */
|
||||
var lastYpos = 0;
|
||||
const onscroll = (evnt = {}) => {
|
||||
/** Current scroll position. */
|
||||
var pos = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
/** Difference between current scroll position and previous scroll position. */
|
||||
var dif = lastYpos - pos;
|
||||
/** Direction of the scroll. */
|
||||
var dir = dif < 0 ? 'down' : 'up';
|
||||
dif = Math.abs(dif);
|
||||
lastYpos = pos;
|
||||
// If not pinned / scrolled out the viewport.
|
||||
if (this.state == Mhead.UNPINNED) {
|
||||
// If scrolling up
|
||||
if (dir == 'up') {
|
||||
// If scrolling fast enough or past minimum
|
||||
if (pos < this.opts.scroll.pin ||
|
||||
dif > this.opts.scroll.tolerance) {
|
||||
this.pin();
|
||||
}
|
||||
}
|
||||
}
|
||||
// If pinned / not scrolled out the viewport.
|
||||
else if (this.state == Mhead.PINNED) {
|
||||
// If scrolling down.
|
||||
if (dir == 'down') {
|
||||
// If scrolling fast enough and past minimum.
|
||||
if (pos > this.opts.scroll.unpin &&
|
||||
dif > this.opts.scroll.tolerance) {
|
||||
this.unpin();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.pin();
|
||||
}
|
||||
};
|
||||
window.addEventListener('scroll', onscroll, {
|
||||
passive: true
|
||||
});
|
||||
onscroll();
|
||||
}
|
||||
/**
|
||||
* Pin the header to the top of the viewport.
|
||||
*/
|
||||
pin() {
|
||||
this.header.classList.add('mh-pinned');
|
||||
this.header.classList.remove('mh-unpinned');
|
||||
this.state = Mhead.PINNED;
|
||||
this.trigger('pinned');
|
||||
}
|
||||
/**
|
||||
* Release the header from the top of the viewport.
|
||||
*/
|
||||
unpin() {
|
||||
this.header.classList.remove('mh-pinned');
|
||||
this.header.classList.add('mh-unpinned');
|
||||
this.state = Mhead.UNPINNED;
|
||||
this.trigger('unpinned');
|
||||
}
|
||||
/**
|
||||
* Bind the hooks specified in the options (publisher).
|
||||
*/
|
||||
initHooks() {
|
||||
this.hooks = {};
|
||||
for (let hook in this.opts.hooks) {
|
||||
this.bind(hook, this.opts.hooks[hook]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Bind functions to a hook (subscriber).
|
||||
* @param {string} hook The hook.
|
||||
* @param {function} func The function.
|
||||
*/
|
||||
bind(hook, func) {
|
||||
// Create an array for the hook if it does not yet excist.
|
||||
this.hooks[hook] = this.hooks[hook] || [];
|
||||
// Push the function to the array.
|
||||
this.hooks[hook].push(func);
|
||||
}
|
||||
/**
|
||||
* Invoke the functions bound to a hook (publisher).
|
||||
* @param {string} hook The hook.
|
||||
* @param {array} [args] Arguments for the function.
|
||||
*/
|
||||
trigger(hook, args) {
|
||||
if (this.hooks[hook]) {
|
||||
for (var h = 0, l = this.hooks[hook].length; h < l; h++) {
|
||||
this.hooks[hook][h].apply(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Plugin version. */
|
||||
Mhead.version = version;
|
||||
/** Default options for headers. */
|
||||
Mhead.options = options;
|
||||
/** State for a "pinned" header. */
|
||||
Mhead.PINNED = 'pinned';
|
||||
/** State for a "unpinned" header. */
|
||||
Mhead.UNPINNED = 'unpinned';
|
||||
8
dist/core/options.js
vendored
Normal file
8
dist/core/options.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
hooks: {},
|
||||
scroll: {
|
||||
hide: 0,
|
||||
show: 0,
|
||||
tolerance: 5
|
||||
}
|
||||
};
|
||||
10
dist/mhead.css
vendored
Normal file
10
dist/mhead.css
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/*!
|
||||
* mhead.js
|
||||
* mmenu.frebsite.nl/mhead-plugin
|
||||
*
|
||||
* Copyright (c) Fred Heusschen
|
||||
* www.frebsite.nl
|
||||
*
|
||||
* License: CC-BY-4.0
|
||||
* http://creativecommons.org/licenses/by/4.0/
|
||||
*/.mh-sticky{position:-webkit-sticky;position:sticky;top:0;z-index:10;-webkit-transition:-webkit-transform .2s ease;transition:-webkit-transform .2s ease;-o-transition:transform .2s ease;transition:transform .2s ease;transition:transform .2s ease,-webkit-transform .2s ease;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.mh-sticky.mh-unpinned{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}
|
||||
12
dist/mhead.js
vendored
Normal file
12
dist/mhead.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
!function(t){var s={};function e(n){if(s[n])return s[n].exports;var i=s[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}e.m=t,e.c=s,e.d=function(t,s,n){e.o(t,s)||Object.defineProperty(t,s,{enumerable:!0,get:n})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,s){if(1&s&&(t=e(t)),8&s)return t;if(4&s&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(e.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&s&&"string"!=typeof t)for(var i in t)e.d(n,i,function(s){return t[s]}.bind(null,i));return n},e.n=function(t){var s=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(s,"a",s),s},e.o=function(t,s){return Object.prototype.hasOwnProperty.call(t,s)},e.p="",e(e.s=0)}([function(t,s,e){"use strict";e.r(s);var n,i={hooks:{},scroll:{pin:0,unpin:0,tolerance:5}};class o{constructor(t,s=o.options){this.header="string"==typeof t?document.querySelector(t):t,t&&(this.opts=Object.assign(s,o.options),this.initHooks(),this.initScroll())}initScroll(){if(!this.opts.scroll||!1===this.opts.scroll.unpin)return;this.header.classList.add("mh-sticky");var t=2*this.header.offsetHeight;this.opts.scroll.unpin=Math.max(t,this.opts.scroll.unpin||0),this.opts.scroll.pin=Math.max(t,this.opts.scroll.pin||0),this.state=null;var s=0;const e=(t={})=>{var e=document.documentElement.scrollTop||document.body.scrollTop,n=s-e,i=n<0?"down":"up";n=Math.abs(n),s=e,this.state==o.UNPINNED?"up"==i&&(e<this.opts.scroll.pin||n>this.opts.scroll.tolerance)&&this.pin():this.state==o.PINNED?"down"==i&&e>this.opts.scroll.unpin&&n>this.opts.scroll.tolerance&&this.unpin():this.pin()};window.addEventListener("scroll",e,{passive:!0}),e()}pin(){this.header.classList.add("mh-pinned"),this.header.classList.remove("mh-unpinned"),this.state=o.PINNED,this.trigger("pinned")}unpin(){this.header.classList.remove("mh-pinned"),this.header.classList.add("mh-unpinned"),this.state=o.UNPINNED,this.trigger("unpinned")}initHooks(){this.hooks={};for(let t in this.opts.hooks)this.bind(t,this.opts.hooks[t])}bind(t,s){this.hooks[t]=this.hooks[t]||[],this.hooks[t].push(s)}trigger(t,s){if(this.hooks[t])for(var e=0,n=this.hooks[t].length;e<n;e++)this.hooks[t][e].apply(this,s)}}o.version="2.0.0",o.options=i,o.PINNED="pinned",o.UNPINNED="unpinned",
|
||||
/*!
|
||||
* mhead.js
|
||||
* mmenu.frebsite.nl/mhead-plugin
|
||||
*
|
||||
* Copyright (c) Fred Heusschen
|
||||
* www.frebsite.nl
|
||||
*
|
||||
* License: CC-BY-4.0
|
||||
* http://creativecommons.org/licenses/by/4.0/
|
||||
*/
|
||||
window.Mhead=o,(n=window.jQuery||window.Zepto||null)&&(n.fn.mhead=function(t){return this.each((s,e)=>{new o(e,t)})})}]);
|
||||
66
gulpfile.js
Normal file
66
gulpfile.js
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Tasks:
|
||||
|
||||
$ gulp : Runs "css" and "js" tasks
|
||||
$ gulp watch : Starts a watch on "css" and "js" tasks
|
||||
*/
|
||||
|
||||
const gulp = require('gulp');
|
||||
const sass = require('gulp-sass');
|
||||
const autoprefixer = require('gulp-autoprefixer');
|
||||
const cleancss = require('gulp-clean-css');
|
||||
const typescript = require('gulp-typescript');
|
||||
const webpack = require('webpack-stream');
|
||||
|
||||
const inputDir = 'src';
|
||||
const outputDir = 'dist';
|
||||
|
||||
exports.css = css = () => {
|
||||
return gulp
|
||||
.src(inputDir + '/*.scss')
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(autoprefixer(['> 5%', 'last 5 versions']))
|
||||
.pipe(cleancss())
|
||||
.pipe(gulp.dest(outputDir));
|
||||
};
|
||||
|
||||
// Transpile all TS files to JS.
|
||||
const JStranspile = cb => {
|
||||
return gulp
|
||||
.src(inputDir + '/**/*.ts')
|
||||
.pipe(
|
||||
typescript({
|
||||
target: 'es6',
|
||||
module: 'es6'
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(outputDir));
|
||||
};
|
||||
|
||||
// Pack the files.
|
||||
const JSpack = () => {
|
||||
return gulp
|
||||
.src(inputDir + '/mhead.js')
|
||||
.pipe(
|
||||
webpack({
|
||||
// mode: 'development',
|
||||
mode: 'production',
|
||||
output: {
|
||||
filename: 'mhead.js'
|
||||
}
|
||||
// optimization: {
|
||||
// minimize: false
|
||||
// }
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(outputDir));
|
||||
};
|
||||
|
||||
exports.js = js = gulp.series(JStranspile, JSpack);
|
||||
exports.default = gulp.parallel(js, css);
|
||||
|
||||
exports.watch = cb => {
|
||||
gulp.watch('src/**/*.ts', js);
|
||||
gulp.watch('src/**/*.scss', css);
|
||||
cb();
|
||||
};
|
||||
26
index.html
Normal file
26
index.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="author" content="www.frebsite.nl" />
|
||||
<meta content="width=600px user-scalable=yes" name="viewport" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
|
||||
<title>mhead.js - sticky mobile navigation headers.</title>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="http://fonts.googleapis.com/css?family=Pacifico" />
|
||||
<link type="text/css" rel="stylesheet" href="demo/css/site.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<div class="phone">
|
||||
<iframe name="phone" src="demo/default.html" frameborder="0" width="320" height="480"></iframe>
|
||||
</div>
|
||||
<div id="page">
|
||||
<h1>mhead</h1>
|
||||
<p>Keep your header in view when appropriate, while saving space the rest of the time.</p>
|
||||
<p>Check out <a href="http://mmenujs.com/mhead-plugin" target="_blank">the documentation</a> for more information.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "mhead-js",
|
||||
"version": "2.0.0",
|
||||
"main": "dist/mhead.js",
|
||||
"module": "src/mhead.js",
|
||||
"author": "Fred Heusschen <info@frebsite.nl>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FrDH/jQuery.mhead.git"
|
||||
},
|
||||
"description": "A small framework for creating a beautiful mobile navigation header to accompany the mmenu.js menu on your website and webapp.",
|
||||
"keywords": [
|
||||
"app",
|
||||
"mobile",
|
||||
"navigation",
|
||||
"head",
|
||||
"header",
|
||||
"navbar"
|
||||
],
|
||||
"license": "CC-BY-NC-4.0",
|
||||
"devDependencies": {
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-autoprefixer": "^6.1.0",
|
||||
"gulp-clean-css": "^4.0.0",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"typescript": "^3.4.4",
|
||||
"webpack-stream": "^5.2.1"
|
||||
}
|
||||
}
|
||||
1
src/_version.ts
Normal file
1
src/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export default '2.0.0';
|
||||
9
src/core/_options.ts
Normal file
9
src/core/_options.ts
Normal file
@ -0,0 +1,9 @@
|
||||
const options: mhOptions = {
|
||||
hooks: {},
|
||||
scroll: {
|
||||
pin: 0,
|
||||
unpin: 0,
|
||||
tolerance: 5
|
||||
}
|
||||
};
|
||||
export default options;
|
||||
30
src/core/_typings.d.ts
vendored
Normal file
30
src/core/_typings.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
/** An object with array of functions values. */
|
||||
interface mhFunctionArrayObject {
|
||||
[key: string]: Function[];
|
||||
}
|
||||
|
||||
/** An object with function values. */
|
||||
interface mhFunctionObject {
|
||||
[key: string]: Function;
|
||||
}
|
||||
|
||||
/** Options for the header. */
|
||||
interface mhOptions {
|
||||
/** Set of hooks for the header. */
|
||||
hooks?: mhFunctionObject;
|
||||
|
||||
/** Scroll options for the header. */
|
||||
scroll?: mhOptionsScroll;
|
||||
}
|
||||
|
||||
/** Scroll options for the header. */
|
||||
interface mhOptionsScroll {
|
||||
/** Minimum scroll position to pin the header when scrolling up. */
|
||||
pin?: number | false;
|
||||
|
||||
/** Minimum scroll position to unpin the header when scrolling down. */
|
||||
unpin?: number | false;
|
||||
|
||||
/** Tolerance for scrolling speed. */
|
||||
tolerance?: number;
|
||||
}
|
||||
11
src/core/mhead.core.scss
Normal file
11
src/core/mhead.core.scss
Normal file
@ -0,0 +1,11 @@
|
||||
.mh-sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
transition: transform 0.2s ease;
|
||||
transform: translate3d(0, 0, 0);
|
||||
|
||||
&.mh-unpinned {
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
}
|
||||
183
src/core/mhead.core.ts
Normal file
183
src/core/mhead.core.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import options from './_options';
|
||||
import version from '../_version';
|
||||
|
||||
/**
|
||||
* Class for a sticky navigational header.
|
||||
*/
|
||||
export default class Mhead {
|
||||
/** Plugin version. */
|
||||
static version: string = version;
|
||||
|
||||
/** Default options for headers. */
|
||||
static options: mhOptions = options;
|
||||
|
||||
/** State for a "pinned" header. */
|
||||
static PINNED: string = 'pinned';
|
||||
|
||||
/** State for a "unpinned" header. */
|
||||
static UNPINNED: string = 'unpinned';
|
||||
|
||||
/** The HTML element for the header. */
|
||||
header: HTMLElement;
|
||||
|
||||
/** Options for the header. */
|
||||
opts: mhOptions;
|
||||
|
||||
/** Callback hooks used for the header. */
|
||||
hooks: mhFunctionArrayObject;
|
||||
|
||||
/** State of the header (pinned or unpinned). */
|
||||
state: string;
|
||||
|
||||
/**
|
||||
* Create a sticky header.
|
||||
* @param {HTMLElement|string} header (Selector for) the header node.
|
||||
* @param {object} [options=Mhead.options] Options for the header.
|
||||
*/
|
||||
constructor(header: HTMLElement | string, options: mhOptions = {}) {
|
||||
// Get header node from string or element.
|
||||
this.header =
|
||||
typeof header == 'string' ? document.querySelector(header) : header;
|
||||
|
||||
// Stop if there is no header element found.
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extend options from defaults.
|
||||
this.opts = Object.assign(options, Mhead.options);
|
||||
|
||||
this.initHooks();
|
||||
this.initScroll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the scroll functionality.
|
||||
*/
|
||||
initScroll() {
|
||||
if (!this.opts.scroll || this.opts.scroll.unpin === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.header.classList.add('mh-sticky');
|
||||
|
||||
/** Minimum scroll position to unpin / hide the header. */
|
||||
var _min = this.header.offsetHeight * 2;
|
||||
this.opts.scroll.unpin = Math.max(_min, this.opts.scroll.unpin || 0);
|
||||
this.opts.scroll.pin = Math.max(_min, this.opts.scroll.pin || 0);
|
||||
|
||||
this.state = null;
|
||||
|
||||
/** Previous scroll position. */
|
||||
var lastYpos = 0;
|
||||
|
||||
const onscroll = (evnt = {}) => {
|
||||
/** Current scroll position. */
|
||||
var pos =
|
||||
document.documentElement.scrollTop || document.body.scrollTop;
|
||||
|
||||
/** Difference between current scroll position and previous scroll position. */
|
||||
var dif = lastYpos - pos;
|
||||
|
||||
/** Direction of the scroll. */
|
||||
var dir = dif < 0 ? 'down' : 'up';
|
||||
|
||||
dif = Math.abs(dif);
|
||||
lastYpos = pos;
|
||||
|
||||
// If not pinned / scrolled out the viewport.
|
||||
if (this.state == Mhead.UNPINNED) {
|
||||
// If scrolling up
|
||||
if (dir == 'up') {
|
||||
// If scrolling fast enough or past minimum
|
||||
if (
|
||||
pos < this.opts.scroll.pin ||
|
||||
dif > this.opts.scroll.tolerance
|
||||
) {
|
||||
this.pin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If pinned / not scrolled out the viewport.
|
||||
else if (this.state == Mhead.PINNED) {
|
||||
// If scrolling down.
|
||||
if (dir == 'down') {
|
||||
// If scrolling fast enough and past minimum.
|
||||
if (
|
||||
pos > this.opts.scroll.unpin &&
|
||||
dif > this.opts.scroll.tolerance
|
||||
) {
|
||||
this.unpin();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.pin();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', onscroll, {
|
||||
passive: true
|
||||
});
|
||||
|
||||
onscroll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pin the header to the top of the viewport.
|
||||
*/
|
||||
pin() {
|
||||
this.header.classList.add('mh-pinned');
|
||||
this.header.classList.remove('mh-unpinned');
|
||||
this.state = Mhead.PINNED;
|
||||
|
||||
this.trigger('pinned');
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the header from the top of the viewport.
|
||||
*/
|
||||
unpin() {
|
||||
this.header.classList.remove('mh-pinned');
|
||||
this.header.classList.add('mh-unpinned');
|
||||
this.state = Mhead.UNPINNED;
|
||||
|
||||
this.trigger('unpinned');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the hooks specified in the options (publisher).
|
||||
*/
|
||||
initHooks() {
|
||||
this.hooks = {};
|
||||
for (let hook in this.opts.hooks) {
|
||||
this.bind(hook, this.opts.hooks[hook]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind functions to a hook (subscriber).
|
||||
* @param {string} hook The hook.
|
||||
* @param {function} func The function.
|
||||
*/
|
||||
bind(hook: string, func: Function) {
|
||||
// Create an array for the hook if it does not yet excist.
|
||||
this.hooks[hook] = this.hooks[hook] || [];
|
||||
|
||||
// Push the function to the array.
|
||||
this.hooks[hook].push(func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the functions bound to a hook (publisher).
|
||||
* @param {string} hook The hook.
|
||||
* @param {array} [args] Arguments for the function.
|
||||
*/
|
||||
trigger(hook: string, args?: any[]) {
|
||||
if (this.hooks[hook]) {
|
||||
for (var h = 0, l = this.hooks[hook].length; h < l; h++) {
|
||||
this.hooks[hook][h].apply(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/mhead.js
Normal file
26
src/mhead.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* mhead.js
|
||||
* mmenu.frebsite.nl/mhead
|
||||
*
|
||||
* Copyright (c) Fred Heusschen
|
||||
* www.frebsite.nl
|
||||
*
|
||||
* License: CC-BY-4.0
|
||||
* http://creativecommons.org/licenses/by/4.0/
|
||||
*/
|
||||
|
||||
import Mhead from '../dist/core/mhead.core';
|
||||
|
||||
// Global namespace
|
||||
window.Mhead = Mhead;
|
||||
|
||||
// jQuery plugin
|
||||
(function($) {
|
||||
if ($) {
|
||||
$.fn.mhead = function(options) {
|
||||
return this.each((e, element) => {
|
||||
new Mhead(element, options);
|
||||
});
|
||||
};
|
||||
}
|
||||
})(window.jQuery || window.Zepto || null);
|
||||
12
src/mhead.scss
Normal file
12
src/mhead.scss
Normal file
@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* mhead.js
|
||||
* mmenu.frebsite.nl/mhead
|
||||
*
|
||||
* Copyright (c) Fred Heusschen
|
||||
* www.frebsite.nl
|
||||
*
|
||||
* License: CC-BY-4.0
|
||||
* http://creativecommons.org/licenses/by/4.0/
|
||||
*/
|
||||
|
||||
@import 'core/mhead.core';
|
||||
8
tsconfig.json
Normal file
8
tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"target": "es6"
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user