├── .npmrc ├── test ├── specs │ └── index.spec.js └── index.js ├── .eslintignore ├── .gitignore ├── img └── vue.png ├── .babelrc ├── docs ├── main.js └── App.vue ├── .npmignore ├── src ├── index.js └── sticky.js ├── .editorconfig ├── index.html ├── circle.yml ├── LICENSE ├── karma.conf.js ├── .eslintrc.js ├── README.md ├── package.json └── dist ├── min └── vue-sticky-js.min.js ├── vue-sticky-js.js.map └── vue-sticky-js.js /.npmrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/specs/index.spec.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | lib 4 | *.log 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /img/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianaya89/vue-sticky-js/HEAD/img/vue.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | 4 | new Vue({ 5 | el: 'body', 6 | components: { App } 7 | }); 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | build 4 | docs 5 | test 6 | .babelrc 7 | .eslintignore 8 | .eslintrc 9 | karma.conf.js 10 | circle.yml 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import stickyDirective from './sticky'; 2 | 3 | 4 | module.exports = { 5 | install(Vue) { 6 | Vue.directive('sticky', stickyDirective); 7 | }, 8 | 9 | stickyDirective 10 | }; 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Docs | vue-sticky-js 7 | 8 | 9 | 10 |

vue-sticky-js

11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* global */ 2 | 3 | // require all test files (files that ends with .spec.js) 4 | const testsContext = require.context('./specs/', true, /\.spec$/); 5 | testsContext.keys().forEach(testsContext); 6 | 7 | 8 | // require all src files except main.js for coverage. 9 | // you can also change this to match only the subset of files that 10 | // you want coverage for. 11 | const srcContext = require.context('../src', true, /\.js$/); 12 | srcContext.keys().forEach(srcContext); 13 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | timezone: 3 | America/Vancouver 4 | node: 5 | version: 6 6 | 7 | dependencies: 8 | override: 9 | - npm install 10 | - npm install coveralls 11 | - npm install bithound 12 | 13 | test: 14 | override: 15 | - npm test 16 | - COVERALLS_SERVICE_NAME="circleci" COVERALLS_SERVICE_JOB_ID="$CIRCLE_BUILD_NUM" ./node_modules/coveralls/bin/coveralls.js < ./coverage/lcov.info 17 | - ./node_modules/.bin/bithound check BITHOUND_TOKEN 18 | post: 19 | - cp -R ./test/unit/coverage/* $CIRCLE_ARTIFACTS/ 20 | -------------------------------------------------------------------------------- /src/sticky.js: -------------------------------------------------------------------------------- 1 | import Sticky from 'sticky-js'; 2 | 3 | export default { 4 | bind(val) { 5 | this.el.parentElement.setAttribute('data-sticky-container', ''); 6 | this.el.className += ' sticky'; 7 | }, 8 | 9 | update(val) { 10 | if (val) { 11 | if (val.marginTop) { this.el.setAttribute('data-margin-top', val.marginTop); } 12 | if (val.forName) { this.el.setAttribute('data-sticky-for', val.forName); } 13 | if (val.className) { this.el.setAttribute('data-sticky-class', val.className); } 14 | } 15 | 16 | new Sticky('.sticky'); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 <%= authorName %> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | 4 | const baseConfig = require('./webpack.config'); 5 | 6 | const webpackConfig = merge(baseConfig, { 7 | devtool: '#inline-source-map' 8 | }); 9 | 10 | delete webpackConfig.entry; 11 | 12 | webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || []; 13 | 14 | webpackConfig.module.preLoaders.unshift({ 15 | test : /\.js$/, 16 | loader : 'isparta', 17 | include: path.resolve('src/') 18 | }); 19 | 20 | webpackConfig.module.loaders.some((loader, i) => { 21 | if (loader.loader === 'babel') { 22 | loader.include = path.resolve('test/'); 23 | return true; 24 | } 25 | 26 | return false; 27 | }); 28 | 29 | module.exports = function(config) { 30 | config.set({ 31 | browsers: ['PhantomJS'], 32 | frameworks: ['mocha'], 33 | reporters: ['spec', 'coverage'], 34 | files: ['test/index.js'], 35 | preprocessors: { 36 | 'test/index.js': ['webpack', 'sourcemap'] 37 | }, 38 | webpack: webpackConfig, 39 | webpackMiddleware: { noInfo: true }, 40 | colors: true, 41 | logLevel: config.LOG_DISABLE, 42 | coverageReporter: { 43 | dir : './coverage', 44 | reporters: [ 45 | { type: 'lcov', subdir: '.' }, 46 | { type: 'text-summary' } 47 | ] 48 | } 49 | }); 50 | }; 51 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root : true, 3 | extends : 'airbnb-base', 4 | plugins : [ 'html' ], 5 | rules : { 6 | 'no-multi-spaces' : 0, 7 | 'no-underscore-dangle' : [0], 8 | 'consistent-return' : 0, 9 | 'no-unused-expressions' : [2, { 'allowShortCircuit': true }], 10 | 'no-param-reassign' : 0, 11 | 'func-names' : 0, 12 | 'space-before-function-paren' : [2, 'never'], 13 | 'comma-dangle' : [2, 'never'], 14 | 'no-shadow' : 0, 15 | 'guard-for-in' : 0, 16 | 'no-restricted-syntax' : [2, 'WithStatement'], 17 | 'newline-per-chained-call' : [2, { 'ignoreChainWithDepth': 5 }], 18 | 'space-in-parens' : 0, 19 | 'key-spacing' : 0, 20 | 'no-unused-vars' : [2, { 'vars': 'all', 'args': 'none' }], 21 | 'max-len' : 1, 22 | 'padded-blocks' : 0, 23 | 'no-console' : 0, 24 | 'no-continue' : 0, 25 | 'no-extra-boolean-cast' : 0, 26 | 'import/no-extraneous-dependencies': 0, 27 | 'import/newline-after-import' : 0, 28 | 'no-mixed-operators' : 0, 29 | 'import/no-unresolved' : 0, 30 | 'no-prototype-builtins' : 0, 31 | 'no-bitwise' : ['error', { 'allow': ['~'] }], 32 | 'no-new' : 0 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-sticky-js 2 | 3 | >🏏 Vue.js directive to make sticky elements built with [sticky-js](https://github.com/rgalus/sticky-js) 4 | 5 | **⚠️ Vue.js 2 is not supported yet. [WIP]** 6 | 7 | 8 | ## Prerequisites 9 | [Vue.js](https://github.com/vuejs/vue) 10 | 11 | ## Installation 12 | `$ npm i -S vue-sticky-js` 13 | 14 | ## Directive 15 | `v-sticky` => Define a new sticky element. 16 | 17 | ## Implementation 18 | 19 | ### Global 20 | `main.js` 21 | ```javascript 22 | import Vue from 'vue'; 23 | import VueSticky from 'vue-sticky-js'; 24 | 25 | Vue.use(VueSticky.install); 26 | 27 | // ... 28 | ``` 29 | 30 | `Component.vue` 31 | ```html 32 | 48 | 49 | 54 | 55 | ``` 56 | 57 | ### Component 58 | 59 | `Component.vue` 60 | ```html 61 | 73 | 74 | 79 | ``` 80 | 81 | ## Development Setup 82 | ```bash 83 | # install dependencies 84 | $ npm install 85 | 86 | # dev mode 87 | $ npm run dev 88 | 89 | # test 90 | $ npm run test 91 | 92 | # build 93 | $ npm run build 94 | ``` 95 | 96 | **This project was built with [yeoman](http://yeoman.io/) and [generator-vue-component](https://github.com/ianaya89/generator-vue-component) :heart:** 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-sticky-js", 3 | "version": "1.0.0", 4 | "description": "Vue.js directive to make sticky elements built on sticky-js", 5 | "main": "dist/vue-sticky-js.min.js", 6 | "author": { 7 | "name": "Ignacio Anaya", 8 | "email": "ignacio.anaya89@gmail.com" 9 | }, 10 | "license": "MIT", 11 | "repository": { 12 | "type" : "git", 13 | "url" : "https://github.com/ianaya89/vue-sticky-js" 14 | }, 15 | "scripts": { 16 | "dev": "webpack-dev-server --inline --hot --quiet --port 8080 --open --config build/webpack.config.js", 17 | "build-all": "npm run build && npm run build-docs", 18 | "build-docs": "webpack --progress --hide-modules --config build/webpack.config.js && set NODE_ENV=production webpack --progress --hide-modules", 19 | "build": "webpack --progress --hide-modules --config build/webpack.build.min.js && webpack --progress --hide-modules --config build/webpack.build.js", 20 | "prepublish": "npm run build-all", 21 | "eslint": "eslint --ext .js,.vue src" 22 | }, 23 | "devDependencies": { 24 | "babel-core": "^6.1.21", 25 | "babel-loader": "^6.1.0", 26 | "babel-plugin-transform-runtime": "^6.1.18", 27 | "babel-preset-es2015": "^6.1.18", 28 | "babel-runtime": "^6.3.19", 29 | "css-loader": "^0.21.0", 30 | "eslint": "^3.5.0", 31 | "eslint-config-airbnb": "^11.1.0", 32 | "eslint-loader": "^1.5.0", 33 | "eslint-plugin-html": "^1.5.2", 34 | "eslint-plugin-import": "^1.15.0", 35 | "eslint-plugin-jsx-a11y": "^2.2.2", 36 | "eslint-plugin-react": "^6.2.1", 37 | "extract-text-webpack-plugin": "^1.0.1", 38 | "less": "^2.5.3", 39 | "less-loader": "^2.2.1", 40 | "node-sass": "^3.4.1", 41 | "prismjs": "^1.5.1", 42 | "pug": "^2.0.0-beta6", 43 | "sass-loader": "^3.1.1", 44 | "style-loader": "^0.13.0", 45 | "url-loader": "^0.5.7", 46 | "vue": "^1.0.26", 47 | "vue-hot-reload-api": "^1.2.0", 48 | "vue-html-loader": "^1.2.3", 49 | "vue-loader": "8.5.3", 50 | "vue-style-loader": "^1.0.0", 51 | "webpack": "^1.12.2", 52 | "webpack-dev-server": "^1.12.0" 53 | }, 54 | "pre-commit": [ 55 | "eslint" 56 | ], 57 | "dependencies": { 58 | "sticky-js": "^1.1.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/App.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 38 | 39 | 68 | -------------------------------------------------------------------------------- /dist/min/vue-sticky-js.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports["vue-sticky-js"]=e():t["vue-sticky-js"]=e()}(this,function(){return function(t){function e(s){if(i[s])return i[s].exports;var o=i[s]={exports:{},id:s,loaded:!1};return t[s].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var i={};return e.m=t,e.c=i,e.p="",e(0)}([function(t,e,i){"use strict";function s(t){return t&&t.__esModule?t:{default:t}}var o=i(1),c=s(o);t.exports={install:function(t){t.directive("sticky",c.default)},stickyDirective:c.default}},function(t,e,i){"use strict";function s(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var o=i(2),c=s(o);e.default={bind:function(t){this.el.parentElement.setAttribute("data-sticky-container",""),this.el.className+=" sticky"},update:function(t){t&&(t.marginTop&&this.el.setAttribute("data-margin-top",t.marginTop),t.forName&&this.el.setAttribute("data-sticky-for",t.forName),t.className&&this.el.setAttribute("data-sticky-class",t.className)),new c.default(".sticky")}}},function(t,e,i){var s=i(3);t.exports=s},function(t,e,i){function s(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}/** 2 | * Sticky.js 3 | * Library for sticky elements written in vanilla javascript. With this library you can easily set sticky elements on your website. It's also responsive. 4 | * 5 | * @version 1.2.0 6 | * @author Rafal Galus 7 | * @website https://rgalus.github.io/sticky-js/ 8 | * @repo https://github.com/rgalus/sticky-js 9 | * @license https://github.com/rgalus/sticky-js/blob/master/LICENSE 10 | */ 11 | var o=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};s(this,t),this.selector=e,this.elements=[],this.version="1.2.0",this.vp=this.getViewportSize(),this.body=document.querySelector("body"),this.options={wrap:i.wrap||!1,marginTop:i.marginTop||0,stickyFor:i.stickyFor||0,stickyClass:i.stickyClass||null,stickyContainer:i.stickyContainer||"body"},this.updateScrollTopPosition=this.updateScrollTopPosition.bind(this),this.updateScrollTopPosition(),window.addEventListener("load",this.updateScrollTopPosition),window.addEventListener("scroll",this.updateScrollTopPosition),this.run()}return t.prototype.run=function(){var t=this,e=setInterval(function(){if("complete"===document.readyState){clearInterval(e);var i=document.querySelectorAll(t.selector);t.forEach(i,function(e){return t.renderElement(e)})}},10)},t.prototype.renderElement=function(t){var e=this;t.sticky={},t.sticky.active=!1,t.sticky.marginTop=parseInt(t.getAttribute("data-margin-top"))||this.options.marginTop,t.sticky.stickyFor=parseInt(t.getAttribute("data-sticky-for"))||this.options.stickyFor,t.sticky.stickyClass=t.getAttribute("data-sticky-class")||this.options.stickyClass,t.sticky.wrap=!!t.hasAttribute("data-sticky-wrap")||this.options.wrap,t.sticky.stickyContainer=this.options.stickyContainer,t.sticky.container=this.getStickyContainer(t),t.sticky.container.rect=this.getRectangle(t.sticky.container),t.sticky.rect=this.getRectangle(t),"img"===t.tagName.toLowerCase()&&(t.onload=function(){return t.sticky.rect=e.getRectangle(t)}),t.sticky.wrap&&this.wrapElement(t),this.activate(t)},t.prototype.wrapElement=function(t){t.insertAdjacentHTML("beforebegin",""),t.previousSibling.appendChild(t)},t.prototype.activate=function(t){t.sticky.rect.top+t.sticky.rect.height=t.sticky.container.rect.top+t.sticky.container.rect.height||t.sticky.stickyFor>=this.vp.width&&t.sticky.active)&&(t.sticky.active=!1),this.setPosition(t)},t.prototype.initScrollEvents=function(t){var e=this;t.sticky.scrollListener=function(){return e.onScrollEvents(t)},window.addEventListener("scroll",t.sticky.scrollListener)},t.prototype.destroyScrollEvents=function(t){window.removeEventListener("scroll",t.sticky.scrollListener)},t.prototype.onScrollEvents=function(t){t.sticky.active&&this.setPosition(t)},t.prototype.setPosition=function(t){this.css(t,{position:"",width:"",top:"",left:""}),this.vp.heightt.sticky.rect.top-t.sticky.marginTop?(this.css(t,{position:"fixed",width:t.sticky.rect.width+"px",left:t.sticky.rect.left+"px"}),this.scrollTop+t.sticky.rect.height+t.sticky.marginTop>t.sticky.container.rect.top+t.sticky.container.offsetHeight?(t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{top:t.sticky.container.rect.top+t.sticky.container.offsetHeight-(this.scrollTop+t.sticky.rect.height)+"px"})):(t.sticky.stickyClass&&t.classList.add(t.sticky.stickyClass),this.css(t,{top:t.sticky.marginTop+"px"}))):(t.sticky.stickyClass&&t.classList.remove(t.sticky.stickyClass),this.css(t,{position:"",width:"",top:"",left:""}),t.sticky.wrap&&this.css(t.parentNode,{display:"",width:"",height:""})))},t.prototype.update=function(){var t=this;this.forEach(this.elements,function(e){e.sticky.rect=t.getRectangle(e),e.sticky.container.rect=t.getRectangle(e.sticky.container),t.activate(e),t.setPosition(e)})},t.prototype.destroy=function(){var t=this;this.forEach(this.elements,function(e){t.destroyResizeEvents(e),t.destroyScrollEvents(e),delete e.sticky})},t.prototype.getStickyContainer=function(t){for(var e=t.parentNode;!e.hasAttribute("data-sticky-container")&&!e.parentNode.querySelector(t.sticky.stickyContainer)&&e!==this.body;)e=e.parentNode;return e},t.prototype.getRectangle=function(t){this.css(t,{position:"",width:"",top:"",left:""});var e=Math.max(t.offsetWidth,t.clientWidth,t.scrollWidth),i=Math.max(t.offsetHeight,t.clientHeight,t.scrollHeight),s=0,o=0;do s+=t.offsetTop||0,o+=t.offsetLeft||0,t=t.offsetParent;while(t);return{top:s,left:o,width:e,height:i}},t.prototype.getViewportSize=function(){return{width:Math.max(document.documentElement.clientWidth,window.innerWidth||0),height:Math.max(document.documentElement.clientHeight,window.innerHeight||0)}},t.prototype.updateScrollTopPosition=function(){this.scrollTop=(window.pageYOffset||document.scrollTop)-(document.clientTop||0)||0},t.prototype.forEach=function(t,e){for(var i=0,s=t.length;i\n * @website https://rgalus.github.io/sticky-js/\n * @repo https://github.com/rgalus/sticky-js\n * @license https://github.com/rgalus/sticky-js/blob/master/LICENSE\n */\n\nvar Sticky = function () {\n /**\n * Sticky instance constructor\n * @constructor\n * @param {string} selector - Selector which we can find elements\n * @param {string} options - Global options for sticky elements (could be overwritten by data-{option}=\"\" attributes)\n */\n function Sticky() {\n var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n\n _classCallCheck(this, Sticky);\n\n this.selector = selector;\n this.elements = [];\n\n this.version = '1.2.0';\n\n this.vp = this.getViewportSize();\n this.body = document.querySelector('body');\n\n this.options = {\n wrap: options.wrap || false,\n marginTop: options.marginTop || 0,\n stickyFor: options.stickyFor || 0,\n stickyClass: options.stickyClass || null,\n stickyContainer: options.stickyContainer || 'body'\n };\n\n this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this);\n\n this.updateScrollTopPosition();\n window.addEventListener('load', this.updateScrollTopPosition);\n window.addEventListener('scroll', this.updateScrollTopPosition);\n\n this.run();\n }\n\n /**\n * Function that waits for page to be fully loaded and then renders & activates every sticky element found with specified selector\n * @function\n */\n\n\n Sticky.prototype.run = function run() {\n var _this = this;\n\n // wait for page to be fully loaded\n var pageLoaded = setInterval(function () {\n if (document.readyState === 'complete') {\n clearInterval(pageLoaded);\n\n var elements = document.querySelectorAll(_this.selector);\n _this.forEach(elements, function (element) {\n return _this.renderElement(element);\n });\n }\n }, 10);\n };\n\n /**\n * Function that assign needed variables for sticky element, that are used in future for calculations and other\n * @function\n * @param {node} element - Element to be rendered\n */\n\n\n Sticky.prototype.renderElement = function renderElement(element) {\n var _this2 = this;\n\n // create container for variables needed in future\n element.sticky = {};\n\n // set default variables\n element.sticky.active = false;\n\n element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop;\n element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;\n element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;\n element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap;\n // @todo attribute for stickyContainer\n // element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer;\n element.sticky.stickyContainer = this.options.stickyContainer;\n\n element.sticky.container = this.getStickyContainer(element);\n element.sticky.container.rect = this.getRectangle(element.sticky.container);\n\n element.sticky.rect = this.getRectangle(element);\n\n // fix when element is image that has not yet loaded and width, height = 0\n if (element.tagName.toLowerCase() === 'img') {\n element.onload = function () {\n return element.sticky.rect = _this2.getRectangle(element);\n };\n }\n\n if (element.sticky.wrap) {\n this.wrapElement(element);\n }\n\n // activate rendered element\n this.activate(element);\n };\n\n /**\n * Wraps element into placeholder element\n * @function\n * @param {node} element - Element to be wrapped\n */\n\n\n Sticky.prototype.wrapElement = function wrapElement(element) {\n element.insertAdjacentHTML('beforebegin', '');\n element.previousSibling.appendChild(element);\n };\n\n /**\n * Function that activates element when specified conditions are met and then initalise events\n * @function\n * @param {node} element - Element to be activated\n */\n\n\n Sticky.prototype.activate = function activate(element) {\n if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {\n element.sticky.active = true;\n }\n\n if (this.elements.indexOf(element) < 0) {\n this.elements.push(element);\n }\n\n if (!element.sticky.resizeEvent) {\n this.initResizeEvents(element);\n element.sticky.resizeEvent = true;\n }\n\n if (!element.sticky.scrollEvent) {\n this.initScrollEvents(element);\n element.sticky.scrollEvent = true;\n }\n\n this.setPosition(element);\n };\n\n /**\n * Function which is adding onResizeEvents to window listener and assigns function to element as resizeListener\n * @function\n * @param {node} element - Element for which resize events are initialised\n */\n\n\n Sticky.prototype.initResizeEvents = function initResizeEvents(element) {\n var _this3 = this;\n\n element.sticky.resizeListener = function () {\n return _this3.onResizeEvents(element);\n };\n window.addEventListener('resize', element.sticky.resizeListener);\n };\n\n /**\n * Removes element listener from resize event\n * @function\n * @param {node} element - Element from which listener is deleted\n */\n\n\n Sticky.prototype.destroyResizeEvents = function destroyResizeEvents(element) {\n window.removeEventListener('resize', element.sticky.resizeListener);\n };\n\n /**\n * Function which is fired when user resize window. It checks if element should be activated or deactivated and then run setPosition function\n * @function\n * @param {node} element - Element for which event function is fired\n */\n\n\n Sticky.prototype.onResizeEvents = function onResizeEvents(element) {\n this.vp = this.getViewportSize();\n\n element.sticky.rect = this.getRectangle(element);\n element.sticky.container.rect = this.getRectangle(element.sticky.container);\n\n if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) {\n element.sticky.active = true;\n } else if (element.sticky.rect.top + element.sticky.rect.height >= element.sticky.container.rect.top + element.sticky.container.rect.height || element.sticky.stickyFor >= this.vp.width && element.sticky.active) {\n element.sticky.active = false;\n }\n\n this.setPosition(element);\n };\n\n /**\n * Function which is adding onScrollEvents to window listener and assigns function to element as scrollListener\n * @function\n * @param {node} element - Element for which scroll events are initialised\n */\n\n\n Sticky.prototype.initScrollEvents = function initScrollEvents(element) {\n var _this4 = this;\n\n element.sticky.scrollListener = function () {\n return _this4.onScrollEvents(element);\n };\n window.addEventListener('scroll', element.sticky.scrollListener);\n };\n\n /**\n * Removes element listener from scroll event\n * @function\n * @param {node} element - Element from which listener is deleted\n */\n\n\n Sticky.prototype.destroyScrollEvents = function destroyScrollEvents(element) {\n window.removeEventListener('scroll', element.sticky.scrollListener);\n };\n\n /**\n * Function which is fired when user scroll window. If element is active, function is invoking setPosition function\n * @function\n * @param {node} element - Element for which event function is fired\n */\n\n\n Sticky.prototype.onScrollEvents = function onScrollEvents(element) {\n if (element.sticky.active) {\n this.setPosition(element);\n }\n };\n\n /**\n * Main function for the library. Here are some condition calculations and css appending for sticky element when user scroll window\n * @function\n * @param {node} element - Element that will be positioned if it's active\n */\n\n\n Sticky.prototype.setPosition = function setPosition(element) {\n this.css(element, { position: '', width: '', top: '', left: '' });\n\n if (this.vp.height < element.sticky.rect.height || !element.sticky.active) {\n return;\n }\n\n if (!element.sticky.rect.width) {\n element.sticky.rect = this.getRectangle(element);\n }\n\n if (element.sticky.wrap) {\n this.css(element.parentNode, {\n display: 'block',\n width: element.sticky.rect.width + 'px',\n height: element.sticky.rect.height + 'px'\n });\n }\n\n if (element.sticky.rect.top === 0 && element.sticky.container === this.body) {\n this.css(element, {\n position: 'fixed',\n top: element.sticky.rect.top + 'px',\n left: element.sticky.rect.left + 'px',\n width: element.sticky.rect.width + 'px'\n });\n } else if (this.scrollTop > element.sticky.rect.top - element.sticky.marginTop) {\n this.css(element, {\n position: 'fixed',\n width: element.sticky.rect.width + 'px',\n left: element.sticky.rect.left + 'px'\n });\n\n if (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight) {\n\n if (element.sticky.stickyClass) {\n element.classList.remove(element.sticky.stickyClass);\n }\n\n this.css(element, {\n top: element.sticky.container.rect.top + element.sticky.container.offsetHeight - (this.scrollTop + element.sticky.rect.height) + 'px' });\n } else {\n if (element.sticky.stickyClass) {\n element.classList.add(element.sticky.stickyClass);\n }\n\n this.css(element, { top: element.sticky.marginTop + 'px' });\n }\n } else {\n if (element.sticky.stickyClass) {\n element.classList.remove(element.sticky.stickyClass);\n }\n\n this.css(element, { position: '', width: '', top: '', left: '' });\n\n if (element.sticky.wrap) {\n this.css(element.parentNode, { display: '', width: '', height: '' });\n }\n }\n };\n\n /**\n * Function that updates element sticky rectangle (with sticky container), then activate or deactivate element, then update position if it's active\n * @function\n */\n\n\n Sticky.prototype.update = function update() {\n var _this5 = this;\n\n this.forEach(this.elements, function (element) {\n element.sticky.rect = _this5.getRectangle(element);\n element.sticky.container.rect = _this5.getRectangle(element.sticky.container);\n\n _this5.activate(element);\n _this5.setPosition(element);\n });\n };\n\n /**\n * Destroys sticky element, remove listeners\n * @function\n */\n\n\n Sticky.prototype.destroy = function destroy() {\n var _this6 = this;\n\n this.forEach(this.elements, function (element) {\n _this6.destroyResizeEvents(element);\n _this6.destroyScrollEvents(element);\n delete element.sticky;\n });\n };\n\n /**\n * Function that returns container element in which sticky element is stuck (if is not specified, then it's stuck to body)\n * @function\n * @param {node} element - Element which sticky container are looked for\n * @return {node} element - Sticky container\n */\n\n\n Sticky.prototype.getStickyContainer = function getStickyContainer(element) {\n var container = element.parentNode;\n\n while (!container.hasAttribute('data-sticky-container') && !container.parentNode.querySelector(element.sticky.stickyContainer) && container !== this.body) {\n container = container.parentNode;\n }\n\n return container;\n };\n\n /**\n * Function that returns element rectangle & position (width, height, top, left)\n * @function\n * @param {node} element - Element which position & rectangle are returned\n * @return {object}\n */\n\n\n Sticky.prototype.getRectangle = function getRectangle(element) {\n this.css(element, { position: '', width: '', top: '', left: '' });\n\n var width = Math.max(element.offsetWidth, element.clientWidth, element.scrollWidth);\n var height = Math.max(element.offsetHeight, element.clientHeight, element.scrollHeight);\n\n var top = 0;\n var left = 0;\n\n do {\n top += element.offsetTop || 0;\n left += element.offsetLeft || 0;\n element = element.offsetParent;\n } while (element);\n\n return { top: top, left: left, width: width, height: height };\n };\n\n /**\n * Function that returns viewport dimensions\n * @function\n * @return {object}\n */\n\n\n Sticky.prototype.getViewportSize = function getViewportSize() {\n return {\n width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),\n height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0)\n };\n };\n\n /**\n * Function that updates window scroll position\n * @function\n * @return {number}\n */\n\n\n Sticky.prototype.updateScrollTopPosition = function updateScrollTopPosition() {\n this.scrollTop = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0) || 0;\n };\n\n /**\n * Helper function for loops\n * @helper\n * @param {array}\n * @param {function} callback - Callback function (no need for explanation)\n */\n\n\n Sticky.prototype.forEach = function forEach(array, callback) {\n for (var i = 0, len = array.length; i < len; i++) {\n callback(array[i]);\n }\n };\n\n /**\n * Helper function to add/remove css properties for specified element.\n * @helper\n * @param {node} element - DOM element\n * @param {object} properties - CSS properties that will be added/removed from specified element\n */\n\n\n Sticky.prototype.css = function css(element, properties) {\n for (var property in properties) {\n if (properties.hasOwnProperty(property)) {\n element.style[property] = properties[property];\n }\n }\n };\n\n return Sticky;\n}();\n\n/**\n * Export function that supports AMD, CommonJS and Plain Browser.\n */\n\n\n(function (root, factory) {\n if (typeof exports !== 'undefined') {\n module.exports = factory;\n } else if (typeof define === 'function' && define.amd) {\n define([], factory);\n } else {\n root.Sticky = factory;\n }\n})(this, Sticky);\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/sticky-js/dist/sticky.compile.js\n// module id = 3\n// module chunks = 0"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/vue-sticky-js.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["vue-sticky-js"] = factory(); 8 | else 9 | root["vue-sticky-js"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | /******/ 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ (function(module, exports, __webpack_require__) { 56 | 57 | 'use strict'; 58 | 59 | var _sticky = __webpack_require__(1); 60 | 61 | var _sticky2 = _interopRequireDefault(_sticky); 62 | 63 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 64 | 65 | module.exports = { 66 | install: function install(Vue) { 67 | Vue.directive('sticky', _sticky2.default); 68 | }, 69 | 70 | 71 | stickyDirective: _sticky2.default 72 | }; 73 | 74 | /***/ }), 75 | /* 1 */ 76 | /***/ (function(module, exports, __webpack_require__) { 77 | 78 | 'use strict'; 79 | 80 | Object.defineProperty(exports, "__esModule", { 81 | value: true 82 | }); 83 | 84 | var _stickyJs = __webpack_require__(2); 85 | 86 | var _stickyJs2 = _interopRequireDefault(_stickyJs); 87 | 88 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 89 | 90 | exports.default = { 91 | bind: function bind(val) { 92 | this.el.parentElement.setAttribute('data-sticky-container', ''); 93 | this.el.className += ' sticky'; 94 | }, 95 | update: function update(val) { 96 | if (val) { 97 | if (val.marginTop) { 98 | this.el.setAttribute('data-margin-top', val.marginTop); 99 | } 100 | if (val.forName) { 101 | this.el.setAttribute('data-sticky-for', val.forName); 102 | } 103 | if (val.className) { 104 | this.el.setAttribute('data-sticky-class', val.className); 105 | } 106 | } 107 | 108 | new _stickyJs2.default('.sticky'); 109 | } 110 | }; 111 | 112 | /***/ }), 113 | /* 2 */ 114 | /***/ (function(module, exports, __webpack_require__) { 115 | 116 | 117 | var Sticky = __webpack_require__(3); 118 | 119 | module.exports = Sticky; 120 | 121 | 122 | /***/ }), 123 | /* 3 */ 124 | /***/ (function(module, exports, __webpack_require__) { 125 | 126 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 127 | 128 | /** 129 | * Sticky.js 130 | * Library for sticky elements written in vanilla javascript. With this library you can easily set sticky elements on your website. It's also responsive. 131 | * 132 | * @version 1.2.0 133 | * @author Rafal Galus 134 | * @website https://rgalus.github.io/sticky-js/ 135 | * @repo https://github.com/rgalus/sticky-js 136 | * @license https://github.com/rgalus/sticky-js/blob/master/LICENSE 137 | */ 138 | 139 | var Sticky = function () { 140 | /** 141 | * Sticky instance constructor 142 | * @constructor 143 | * @param {string} selector - Selector which we can find elements 144 | * @param {string} options - Global options for sticky elements (could be overwritten by data-{option}="" attributes) 145 | */ 146 | function Sticky() { 147 | var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 148 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 149 | 150 | _classCallCheck(this, Sticky); 151 | 152 | this.selector = selector; 153 | this.elements = []; 154 | 155 | this.version = '1.2.0'; 156 | 157 | this.vp = this.getViewportSize(); 158 | this.body = document.querySelector('body'); 159 | 160 | this.options = { 161 | wrap: options.wrap || false, 162 | marginTop: options.marginTop || 0, 163 | stickyFor: options.stickyFor || 0, 164 | stickyClass: options.stickyClass || null, 165 | stickyContainer: options.stickyContainer || 'body' 166 | }; 167 | 168 | this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this); 169 | 170 | this.updateScrollTopPosition(); 171 | window.addEventListener('load', this.updateScrollTopPosition); 172 | window.addEventListener('scroll', this.updateScrollTopPosition); 173 | 174 | this.run(); 175 | } 176 | 177 | /** 178 | * Function that waits for page to be fully loaded and then renders & activates every sticky element found with specified selector 179 | * @function 180 | */ 181 | 182 | 183 | Sticky.prototype.run = function run() { 184 | var _this = this; 185 | 186 | // wait for page to be fully loaded 187 | var pageLoaded = setInterval(function () { 188 | if (document.readyState === 'complete') { 189 | clearInterval(pageLoaded); 190 | 191 | var elements = document.querySelectorAll(_this.selector); 192 | _this.forEach(elements, function (element) { 193 | return _this.renderElement(element); 194 | }); 195 | } 196 | }, 10); 197 | }; 198 | 199 | /** 200 | * Function that assign needed variables for sticky element, that are used in future for calculations and other 201 | * @function 202 | * @param {node} element - Element to be rendered 203 | */ 204 | 205 | 206 | Sticky.prototype.renderElement = function renderElement(element) { 207 | var _this2 = this; 208 | 209 | // create container for variables needed in future 210 | element.sticky = {}; 211 | 212 | // set default variables 213 | element.sticky.active = false; 214 | 215 | element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop; 216 | element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor; 217 | element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass; 218 | element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap; 219 | // @todo attribute for stickyContainer 220 | // element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer; 221 | element.sticky.stickyContainer = this.options.stickyContainer; 222 | 223 | element.sticky.container = this.getStickyContainer(element); 224 | element.sticky.container.rect = this.getRectangle(element.sticky.container); 225 | 226 | element.sticky.rect = this.getRectangle(element); 227 | 228 | // fix when element is image that has not yet loaded and width, height = 0 229 | if (element.tagName.toLowerCase() === 'img') { 230 | element.onload = function () { 231 | return element.sticky.rect = _this2.getRectangle(element); 232 | }; 233 | } 234 | 235 | if (element.sticky.wrap) { 236 | this.wrapElement(element); 237 | } 238 | 239 | // activate rendered element 240 | this.activate(element); 241 | }; 242 | 243 | /** 244 | * Wraps element into placeholder element 245 | * @function 246 | * @param {node} element - Element to be wrapped 247 | */ 248 | 249 | 250 | Sticky.prototype.wrapElement = function wrapElement(element) { 251 | element.insertAdjacentHTML('beforebegin', ''); 252 | element.previousSibling.appendChild(element); 253 | }; 254 | 255 | /** 256 | * Function that activates element when specified conditions are met and then initalise events 257 | * @function 258 | * @param {node} element - Element to be activated 259 | */ 260 | 261 | 262 | Sticky.prototype.activate = function activate(element) { 263 | if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) { 264 | element.sticky.active = true; 265 | } 266 | 267 | if (this.elements.indexOf(element) < 0) { 268 | this.elements.push(element); 269 | } 270 | 271 | if (!element.sticky.resizeEvent) { 272 | this.initResizeEvents(element); 273 | element.sticky.resizeEvent = true; 274 | } 275 | 276 | if (!element.sticky.scrollEvent) { 277 | this.initScrollEvents(element); 278 | element.sticky.scrollEvent = true; 279 | } 280 | 281 | this.setPosition(element); 282 | }; 283 | 284 | /** 285 | * Function which is adding onResizeEvents to window listener and assigns function to element as resizeListener 286 | * @function 287 | * @param {node} element - Element for which resize events are initialised 288 | */ 289 | 290 | 291 | Sticky.prototype.initResizeEvents = function initResizeEvents(element) { 292 | var _this3 = this; 293 | 294 | element.sticky.resizeListener = function () { 295 | return _this3.onResizeEvents(element); 296 | }; 297 | window.addEventListener('resize', element.sticky.resizeListener); 298 | }; 299 | 300 | /** 301 | * Removes element listener from resize event 302 | * @function 303 | * @param {node} element - Element from which listener is deleted 304 | */ 305 | 306 | 307 | Sticky.prototype.destroyResizeEvents = function destroyResizeEvents(element) { 308 | window.removeEventListener('resize', element.sticky.resizeListener); 309 | }; 310 | 311 | /** 312 | * Function which is fired when user resize window. It checks if element should be activated or deactivated and then run setPosition function 313 | * @function 314 | * @param {node} element - Element for which event function is fired 315 | */ 316 | 317 | 318 | Sticky.prototype.onResizeEvents = function onResizeEvents(element) { 319 | this.vp = this.getViewportSize(); 320 | 321 | element.sticky.rect = this.getRectangle(element); 322 | element.sticky.container.rect = this.getRectangle(element.sticky.container); 323 | 324 | if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) { 325 | element.sticky.active = true; 326 | } else if (element.sticky.rect.top + element.sticky.rect.height >= element.sticky.container.rect.top + element.sticky.container.rect.height || element.sticky.stickyFor >= this.vp.width && element.sticky.active) { 327 | element.sticky.active = false; 328 | } 329 | 330 | this.setPosition(element); 331 | }; 332 | 333 | /** 334 | * Function which is adding onScrollEvents to window listener and assigns function to element as scrollListener 335 | * @function 336 | * @param {node} element - Element for which scroll events are initialised 337 | */ 338 | 339 | 340 | Sticky.prototype.initScrollEvents = function initScrollEvents(element) { 341 | var _this4 = this; 342 | 343 | element.sticky.scrollListener = function () { 344 | return _this4.onScrollEvents(element); 345 | }; 346 | window.addEventListener('scroll', element.sticky.scrollListener); 347 | }; 348 | 349 | /** 350 | * Removes element listener from scroll event 351 | * @function 352 | * @param {node} element - Element from which listener is deleted 353 | */ 354 | 355 | 356 | Sticky.prototype.destroyScrollEvents = function destroyScrollEvents(element) { 357 | window.removeEventListener('scroll', element.sticky.scrollListener); 358 | }; 359 | 360 | /** 361 | * Function which is fired when user scroll window. If element is active, function is invoking setPosition function 362 | * @function 363 | * @param {node} element - Element for which event function is fired 364 | */ 365 | 366 | 367 | Sticky.prototype.onScrollEvents = function onScrollEvents(element) { 368 | if (element.sticky.active) { 369 | this.setPosition(element); 370 | } 371 | }; 372 | 373 | /** 374 | * Main function for the library. Here are some condition calculations and css appending for sticky element when user scroll window 375 | * @function 376 | * @param {node} element - Element that will be positioned if it's active 377 | */ 378 | 379 | 380 | Sticky.prototype.setPosition = function setPosition(element) { 381 | this.css(element, { position: '', width: '', top: '', left: '' }); 382 | 383 | if (this.vp.height < element.sticky.rect.height || !element.sticky.active) { 384 | return; 385 | } 386 | 387 | if (!element.sticky.rect.width) { 388 | element.sticky.rect = this.getRectangle(element); 389 | } 390 | 391 | if (element.sticky.wrap) { 392 | this.css(element.parentNode, { 393 | display: 'block', 394 | width: element.sticky.rect.width + 'px', 395 | height: element.sticky.rect.height + 'px' 396 | }); 397 | } 398 | 399 | if (element.sticky.rect.top === 0 && element.sticky.container === this.body) { 400 | this.css(element, { 401 | position: 'fixed', 402 | top: element.sticky.rect.top + 'px', 403 | left: element.sticky.rect.left + 'px', 404 | width: element.sticky.rect.width + 'px' 405 | }); 406 | } else if (this.scrollTop > element.sticky.rect.top - element.sticky.marginTop) { 407 | this.css(element, { 408 | position: 'fixed', 409 | width: element.sticky.rect.width + 'px', 410 | left: element.sticky.rect.left + 'px' 411 | }); 412 | 413 | if (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight) { 414 | 415 | if (element.sticky.stickyClass) { 416 | element.classList.remove(element.sticky.stickyClass); 417 | } 418 | 419 | this.css(element, { 420 | top: element.sticky.container.rect.top + element.sticky.container.offsetHeight - (this.scrollTop + element.sticky.rect.height) + 'px' }); 421 | } else { 422 | if (element.sticky.stickyClass) { 423 | element.classList.add(element.sticky.stickyClass); 424 | } 425 | 426 | this.css(element, { top: element.sticky.marginTop + 'px' }); 427 | } 428 | } else { 429 | if (element.sticky.stickyClass) { 430 | element.classList.remove(element.sticky.stickyClass); 431 | } 432 | 433 | this.css(element, { position: '', width: '', top: '', left: '' }); 434 | 435 | if (element.sticky.wrap) { 436 | this.css(element.parentNode, { display: '', width: '', height: '' }); 437 | } 438 | } 439 | }; 440 | 441 | /** 442 | * Function that updates element sticky rectangle (with sticky container), then activate or deactivate element, then update position if it's active 443 | * @function 444 | */ 445 | 446 | 447 | Sticky.prototype.update = function update() { 448 | var _this5 = this; 449 | 450 | this.forEach(this.elements, function (element) { 451 | element.sticky.rect = _this5.getRectangle(element); 452 | element.sticky.container.rect = _this5.getRectangle(element.sticky.container); 453 | 454 | _this5.activate(element); 455 | _this5.setPosition(element); 456 | }); 457 | }; 458 | 459 | /** 460 | * Destroys sticky element, remove listeners 461 | * @function 462 | */ 463 | 464 | 465 | Sticky.prototype.destroy = function destroy() { 466 | var _this6 = this; 467 | 468 | this.forEach(this.elements, function (element) { 469 | _this6.destroyResizeEvents(element); 470 | _this6.destroyScrollEvents(element); 471 | delete element.sticky; 472 | }); 473 | }; 474 | 475 | /** 476 | * Function that returns container element in which sticky element is stuck (if is not specified, then it's stuck to body) 477 | * @function 478 | * @param {node} element - Element which sticky container are looked for 479 | * @return {node} element - Sticky container 480 | */ 481 | 482 | 483 | Sticky.prototype.getStickyContainer = function getStickyContainer(element) { 484 | var container = element.parentNode; 485 | 486 | while (!container.hasAttribute('data-sticky-container') && !container.parentNode.querySelector(element.sticky.stickyContainer) && container !== this.body) { 487 | container = container.parentNode; 488 | } 489 | 490 | return container; 491 | }; 492 | 493 | /** 494 | * Function that returns element rectangle & position (width, height, top, left) 495 | * @function 496 | * @param {node} element - Element which position & rectangle are returned 497 | * @return {object} 498 | */ 499 | 500 | 501 | Sticky.prototype.getRectangle = function getRectangle(element) { 502 | this.css(element, { position: '', width: '', top: '', left: '' }); 503 | 504 | var width = Math.max(element.offsetWidth, element.clientWidth, element.scrollWidth); 505 | var height = Math.max(element.offsetHeight, element.clientHeight, element.scrollHeight); 506 | 507 | var top = 0; 508 | var left = 0; 509 | 510 | do { 511 | top += element.offsetTop || 0; 512 | left += element.offsetLeft || 0; 513 | element = element.offsetParent; 514 | } while (element); 515 | 516 | return { top: top, left: left, width: width, height: height }; 517 | }; 518 | 519 | /** 520 | * Function that returns viewport dimensions 521 | * @function 522 | * @return {object} 523 | */ 524 | 525 | 526 | Sticky.prototype.getViewportSize = function getViewportSize() { 527 | return { 528 | width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0), 529 | height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0) 530 | }; 531 | }; 532 | 533 | /** 534 | * Function that updates window scroll position 535 | * @function 536 | * @return {number} 537 | */ 538 | 539 | 540 | Sticky.prototype.updateScrollTopPosition = function updateScrollTopPosition() { 541 | this.scrollTop = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0) || 0; 542 | }; 543 | 544 | /** 545 | * Helper function for loops 546 | * @helper 547 | * @param {array} 548 | * @param {function} callback - Callback function (no need for explanation) 549 | */ 550 | 551 | 552 | Sticky.prototype.forEach = function forEach(array, callback) { 553 | for (var i = 0, len = array.length; i < len; i++) { 554 | callback(array[i]); 555 | } 556 | }; 557 | 558 | /** 559 | * Helper function to add/remove css properties for specified element. 560 | * @helper 561 | * @param {node} element - DOM element 562 | * @param {object} properties - CSS properties that will be added/removed from specified element 563 | */ 564 | 565 | 566 | Sticky.prototype.css = function css(element, properties) { 567 | for (var property in properties) { 568 | if (properties.hasOwnProperty(property)) { 569 | element.style[property] = properties[property]; 570 | } 571 | } 572 | }; 573 | 574 | return Sticky; 575 | }(); 576 | 577 | /** 578 | * Export function that supports AMD, CommonJS and Plain Browser. 579 | */ 580 | 581 | 582 | (function (root, factory) { 583 | if (true) { 584 | module.exports = factory; 585 | } else if (typeof define === 'function' && define.amd) { 586 | define([], factory); 587 | } else { 588 | root.Sticky = factory; 589 | } 590 | })(this, Sticky); 591 | 592 | /***/ }) 593 | /******/ ]) 594 | }); 595 | ; 596 | //# sourceMappingURL=vue-sticky-js.js.map --------------------------------------------------------------------------------