From f3781775ff177843857ad57105c0c34cfeb32972 Mon Sep 17 00:00:00 2001 From: Rick Dullaart Date: Fri, 18 Nov 2022 21:42:43 +0100 Subject: [PATCH] Initial commit --- CONTRIBUTING.md | 47 +++++++ LICENSE.txt | 2 + README.md | 14 +++ composer.json | 15 +++ demo/css/demo.css | 96 +++++++++++++++ demo/css/site.css | 75 ++++++++++++ demo/default.html | 75 ++++++++++++ demo/img/iphonex-example-blue.png | Bin 0 -> 21107 bytes demo/img/iphonex-example-camera.png | Bin 0 -> 2218 bytes dist/_helpers.js | 40 ++++++ dist/_version.js | 1 + dist/core/_options.js | 9 ++ dist/core/mhead.core.js | 138 +++++++++++++++++++++ dist/core/options.js | 8 ++ dist/mhead.css | 10 ++ dist/mhead.js | 12 ++ gulpfile.js | 66 ++++++++++ index.html | 26 ++++ package.json | 30 +++++ src/_version.ts | 1 + src/core/_options.ts | 9 ++ src/core/_typings.d.ts | 30 +++++ src/core/mhead.core.scss | 11 ++ src/core/mhead.core.ts | 183 ++++++++++++++++++++++++++++ src/mhead.js | 26 ++++ src/mhead.scss | 12 ++ tsconfig.json | 8 ++ 27 files changed, 944 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 composer.json create mode 100644 demo/css/demo.css create mode 100644 demo/css/site.css create mode 100644 demo/default.html create mode 100644 demo/img/iphonex-example-blue.png create mode 100644 demo/img/iphonex-example-camera.png create mode 100644 dist/_helpers.js create mode 100644 dist/_version.js create mode 100644 dist/core/_options.js create mode 100644 dist/core/mhead.core.js create mode 100644 dist/core/options.js create mode 100644 dist/mhead.css create mode 100644 dist/mhead.js create mode 100644 gulpfile.js create mode 100644 index.html create mode 100644 package.json create mode 100644 src/_version.ts create mode 100644 src/core/_options.ts create mode 100644 src/core/_typings.d.ts create mode 100644 src/core/mhead.core.scss create mode 100644 src/core/mhead.core.ts create mode 100644 src/mhead.js create mode 100644 src/mhead.scss create mode 100644 tsconfig.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..dfeec2c --- /dev/null +++ b/CONTRIBUTING.md @@ -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. + + + +## 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. + + + +## 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. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d14dbf9 --- /dev/null +++ b/LICENSE.txt @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f48b63 --- /dev/null +++ b/README.md @@ -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.
+Run `gulp watch` in the command-line to put a watch on the files and run all scripts immediately after saving your changes. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b63d7bc --- /dev/null +++ b/composer.json @@ -0,0 +1,15 @@ +{ + "name" : "mhead-js", + "version" : "2.0.0", + "authors" : "Fred Heusschen ", + "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" +} diff --git a/demo/css/demo.css b/demo/css/demo.css new file mode 100644 index 0000000..471d0b0 --- /dev/null +++ b/demo/css/demo.css @@ -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; +} diff --git a/demo/css/site.css b/demo/css/site.css new file mode 100644 index 0000000..3f34b11 --- /dev/null +++ b/demo/css/site.css @@ -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%); +} diff --git a/demo/default.html b/demo/default.html new file mode 100644 index 0000000..e90a453 --- /dev/null +++ b/demo/default.html @@ -0,0 +1,75 @@ + + + + + + + + + mhead.js demo + + + + + + + + +
+ +
+ demo +
+ +
+ + + +
+ +
+

This is a demo.
+ Scroll down to see the header in action.

+
+ +
+

Scroll a bit faster.
+ If the header did not yet disappear.

+
+ +
+

Now scroll back up.
+ To make the header appear again.

+
+
+ + + + + + diff --git a/demo/img/iphonex-example-blue.png b/demo/img/iphonex-example-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..0260b7e162858f008d607fe3de597b00395d1bc1 GIT binary patch literal 21107 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 literal 0 HcmV?d00001 diff --git a/demo/img/iphonex-example-camera.png b/demo/img/iphonex-example-camera.png new file mode 100644 index 0000000000000000000000000000000000000000..fa6e40f20f9ed8827f2170d75e57e08bcacffc7e GIT binary patch literal 2218 zcmaJ@dpJ~iA0NiG_holU)=H)^p*S;h&fF)H+nC(Oy$vtrICC&*F6P20t;NRLc2i5Q zBDXxLcCk`PNjz)S#>*1hE=r{diRf>o##2v{C?l_{d~Tk@9lX`ZcxB> zlQkc$!QpTwKHmHg?7td&tc(n_CuaKw?M3Akt_qdKsZwACigOpqVo`#R1dc~TP*{|j zbPILC;qaN_uy9ql(4ULQBxG3YL)J*-7#fFjan;CSBoS2+V$pc9lt+Bp)IubPMLgn8 zhL9?hd!h+q?|lk1bYDOivM&+gh={J+2re2fMj$~|FhL{PD^+qeJmNAh7dvas6e3~S zMU}`Su80a31`#}E3Y5SgGXVsoGYBjW8D!Gv9F8*qqJnG+l}!O@0LbLhsayymy!jEa zXbMprH-s;E6AQcXhzTl{oJ*mkq@<8jXk?ipo&s_>910bpKoEd=0LoOU3f2HprQIq6 zA5|g>v0NpVNeNm;I98@s@ranxl@ug$q3|uSRQaY*Sji|FSWW@SREk8R&1>0PsR}{= zFXNrw%CJ;9N(n)gGPMH1`VnWh3dU;p_kpyIm>O=NLX0&9-piLEY6&V;`S5u}>d?FFwfTd#+-Aq)HVmMbOo_Vl3_omMQpKEZ0+k!YY{}OeWjAIsrimGL=l3Ad?e3 zJ+&R@5FCXtB9>~6o3%|^$(fHT#L1{gppZ!j%T?xz-=TsL$Al0BW&#icgaA5A!~tL$ z1OhA;N{{8RAQ1>c#5Z`+U#hn{y6M!6Z_&gY%a`S~X$QsXe}33+(cCu}~?tO$uynbbbhahQsMy_u;#TX$Gf@ zf65!;uf5qaMmpRa!1Ni2z8b{WcgkqQ?btygHD)w^Z?lm^UjHY)Q9jbGlT>k+Eq|!{ zIo?QbG)3RS@Z+- z=$&jiU|&#hxYniKuc%%lysPU z|9MT(W6hy)vnx>>teiZ{_Y@F&pZN9{tHb8zAEfSzx;o=DVE2Q#IGvQEbbO|(p51!& zK-v|1%kYTvUA~0d~0V^Z~FMfN6}5n7vcj;Bxl;exJrZVw2>>o zZ$`Zm|R**gd|(Dl%gth-YDT0g%dY|D8g6T#e$NbihhRF_%8{84%3 zx<1YX*LLJ`z8&F(o2QW~UyTpX;$|)Mb(nsyYpNevvyKmoPk#FuSrql+KGDgQPO&$j=gAY`ANTT=b|gFl+_PTq6? zLQGw%Cth?U=;6245=?EfBaH?Er%o$N-1qZrZ#NZwGQ^`e-PrX-)~hz5()`JiO~Rzh z#e&~m{hQJbdStd7CH=99zdrU?10I5`t1B!t4mZ`;++qm(=IdeWvS)0%BdydGIG|Ux z?X1V=xt8!X@)>Y38;)@u?Jga=+c1<-F(v=+Vo~z$JR&S0@*9Q$cRSO2<-(Tk_VP8af}En0VGn{_sP%tg`b} z5Z69(*8k6jZ+siv(;DJz63=fNFANjTp6C1{*Sfm;y&G`J8fS}#j4>NVukYnx*}+pxWoCteIcP$t^*$_69e(B}+FLv=hsY2S+sx z8qRO32Og0M#@|m@%;^sli)=eMI%{9g7kg*U|1-LjG&XfWlzR5UzfNat5iOpxDw|CA po3jE_)bMcZjBRg0LE4f-D{h^QvF}i%C{O$K;Num*ul0z@{2u|jh)w_i literal 0 HcmV?d00001 diff --git a/dist/_helpers.js b/dist/_helpers.js new file mode 100644 index 0000000..f0f2410 --- /dev/null +++ b/dist/_helpers.js @@ -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(); +} diff --git a/dist/_version.js b/dist/_version.js new file mode 100644 index 0000000..ff603d7 --- /dev/null +++ b/dist/_version.js @@ -0,0 +1 @@ +export default '2.0.0'; diff --git a/dist/core/_options.js b/dist/core/_options.js new file mode 100644 index 0000000..0ca36b3 --- /dev/null +++ b/dist/core/_options.js @@ -0,0 +1,9 @@ +const options = { + hooks: {}, + scroll: { + pin: 0, + unpin: 0, + tolerance: 5 + } +}; +export default options; diff --git a/dist/core/mhead.core.js b/dist/core/mhead.core.js new file mode 100644 index 0000000..8073f0c --- /dev/null +++ b/dist/core/mhead.core.js @@ -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'; diff --git a/dist/core/options.js b/dist/core/options.js new file mode 100644 index 0000000..92e730f --- /dev/null +++ b/dist/core/options.js @@ -0,0 +1,8 @@ +export default { + hooks: {}, + scroll: { + hide: 0, + show: 0, + tolerance: 5 + } +}; diff --git a/dist/mhead.css b/dist/mhead.css new file mode 100644 index 0000000..9830a16 --- /dev/null +++ b/dist/mhead.css @@ -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)} \ No newline at end of file diff --git a/dist/mhead.js b/dist/mhead.js new file mode 100644 index 0000000..73e9de5 --- /dev/null +++ b/dist/mhead.js @@ -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&&(ethis.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{new o(e,t)})})}]); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..fd7de1d --- /dev/null +++ b/gulpfile.js @@ -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(); +}; diff --git a/index.html b/index.html new file mode 100644 index 0000000..1dd6744 --- /dev/null +++ b/index.html @@ -0,0 +1,26 @@ + + + + + + + + + mhead.js - sticky mobile navigation headers. + + + + + +
+
+ +
+
+

mhead

+

Keep your header in view when appropriate, while saving space the rest of the time.

+

Check out the documentation for more information.

+
+
+ + diff --git a/package.json b/package.json new file mode 100644 index 0000000..ff64bc6 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "mhead-js", + "version": "2.0.0", + "main": "dist/mhead.js", + "module": "src/mhead.js", + "author": "Fred Heusschen ", + "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" + } +} diff --git a/src/_version.ts b/src/_version.ts new file mode 100644 index 0000000..ff603d7 --- /dev/null +++ b/src/_version.ts @@ -0,0 +1 @@ +export default '2.0.0'; diff --git a/src/core/_options.ts b/src/core/_options.ts new file mode 100644 index 0000000..c57373a --- /dev/null +++ b/src/core/_options.ts @@ -0,0 +1,9 @@ +const options: mhOptions = { + hooks: {}, + scroll: { + pin: 0, + unpin: 0, + tolerance: 5 + } +}; +export default options; diff --git a/src/core/_typings.d.ts b/src/core/_typings.d.ts new file mode 100644 index 0000000..71cdefb --- /dev/null +++ b/src/core/_typings.d.ts @@ -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; +} diff --git a/src/core/mhead.core.scss b/src/core/mhead.core.scss new file mode 100644 index 0000000..f81f5ae --- /dev/null +++ b/src/core/mhead.core.scss @@ -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); + } +} diff --git a/src/core/mhead.core.ts b/src/core/mhead.core.ts new file mode 100644 index 0000000..0c22d53 --- /dev/null +++ b/src/core/mhead.core.ts @@ -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); + } + } + } +} diff --git a/src/mhead.js b/src/mhead.js new file mode 100644 index 0000000..70016bd --- /dev/null +++ b/src/mhead.js @@ -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); diff --git a/src/mhead.scss b/src/mhead.scss new file mode 100644 index 0000000..421f44c --- /dev/null +++ b/src/mhead.scss @@ -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'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9e884cd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "include": [ + "src/**/*" + ], + "compilerOptions": { + "target": "es6" + } +}