├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── build ├── webpack.base.conf.js └── webpack.min.conf.js ├── dist ├── ScrollMagnetContainer.vue ├── ScrollMagnetItem.vue └── vue-scroll-magnet.min.js ├── docs ├── .gitignore ├── _config.yml ├── package.json ├── public │ ├── 2016 │ │ └── 11 │ │ │ └── 29 │ │ │ └── a │ │ │ └── index.html │ ├── archives │ │ └── index.html │ ├── atom.xml │ ├── css │ │ └── apollo.css │ ├── docs │ │ └── index.html │ ├── examples │ │ └── index.html │ ├── favicon.png │ ├── font │ │ ├── sourcesanspro.woff │ │ └── sourcesanspro.woff2 │ ├── index.html │ ├── scss │ │ └── apollo.scss │ └── sitemap.xml ├── scaffolds │ ├── draft.md │ ├── page.md │ └── post.md └── source │ ├── _posts │ └── a.md │ ├── docs │ └── index.md │ └── examples │ └── index.md ├── index.html ├── package.json ├── src ├── ScrollMagnetContainer.vue ├── ScrollMagnetItem.vue └── index.js ├── test └── unit │ ├── .eslintrc │ ├── index.js │ ├── karma.conf.js │ ├── specs │ ├── ScrollMagnetContainer.spec.js │ └── ScrollMagnetItem.spec.js │ └── utils │ └── index.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "ssense/client" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | test/unit/coverage 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-scroll-magnet 2 | 3 | ## What is Vue Scroll Magnet? 4 | 5 | Vue Scroll Magnet is a pair of component wrappers which allow elements to follow the viewport while scrolling and then anchor at the upper and lower extremeties of their parent container. This module is inspired by a jQuery plugin called Sticky kit. 6 | 7 | ## Installation 8 | 9 | > This component relies on ES6 and may not be compatible with Vue 1.x projects. 10 | 11 | ``` bash 12 | npm install -S vue-scroll-magnet 13 | ``` 14 | 15 | ## Usage (Global) 16 | Install the components globally: 17 | ``` js 18 | import Vue from 'vue'; 19 | import VueScrollMagnet from 'vue-scroll-magnet'; 20 | 21 | Vue.use(VueScrollMagnet); 22 | ``` 23 | 24 | ## Usage (Component) 25 | Include the wrappers into your component using import: 26 | ``` js 27 | /* inside your Vue component's script tag */ 28 | 29 | import { ScrollMagnetContainer, ScrollMagnetItem } from 'vue-scroll-magnet'; 30 | 31 | export default { 32 | ... 33 | components: { 34 | ScrollMagnetContainer, 35 | ScrollMagnetItem 36 | } 37 | ... 38 | }; 39 | ``` 40 | 41 | ### HTML structure 42 | The scrollable height boundary will be determined as the direct parent of the <scroll-magnet-container> by default, however it can be configured using an element selector which it can use as a fixed context. 43 | 44 | In the example below, <scroll-magnet-container> will automatically use the top of #app as its upper boundary and its height of 500px as its lower boundary unless a specific element context is provided. 45 | 46 | ``` html 47 |
48 | 49 | This content will scroll 50 | 51 |
52 | ``` 53 | 54 | ### Tests 55 | To run unit tests & code coverage: 56 | ``` bash 57 | npm test 58 | ``` -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const npmCfg = require('../package.json'); 4 | const projectRoot = path.resolve(__dirname, '../'); 5 | 6 | var banner = [ 7 | 'vue-scroll-magnet v' + npmCfg.version, 8 | '(c) ' + (new Date().getFullYear()) + ' ' + npmCfg.author, 9 | npmCfg.homepage 10 | ].join('\n') 11 | 12 | module.exports = { 13 | entry: './src/', 14 | output: { 15 | path: path.resolve(__dirname, '../dist'), 16 | filename: 'vue-scroll-magnet.js', 17 | library: 'VueScrollMagnet', 18 | libraryTarget: 'umd' 19 | }, 20 | resolve: { 21 | extensions: ['', '.js', '.vue'], 22 | fallback: [path.join(__dirname, '../node_modules')], 23 | alias: { 24 | 'src': path.resolve(__dirname, '../src'), 25 | vue: 'vue/dist/vue.js' 26 | } 27 | }, 28 | resolveLoader: { 29 | fallback: [path.join(__dirname, '../node_modules')] 30 | }, 31 | module: { 32 | loaders: [ 33 | { 34 | test: /\.vue$/, 35 | loader: 'vue' 36 | }, 37 | { 38 | test: /\.js$/, 39 | loader: 'babel', 40 | include: projectRoot, 41 | exclude: /node_modules/, 42 | } 43 | ] 44 | }, 45 | vue: { 46 | loaders: { 47 | js: 'babel!eslint' 48 | } 49 | }, 50 | plugins: [ 51 | new webpack.BannerPlugin(banner) 52 | ] 53 | } -------------------------------------------------------------------------------- /build/webpack.min.conf.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const CopyWebpackPlugin = require('copy-webpack-plugin') 3 | const base = require('./webpack.base.conf') 4 | 5 | var config = Object.assign({}, base) 6 | 7 | config.output.filename = 'vue-scroll-magnet.min.js' 8 | 9 | config.plugins = (config.plugins || []).concat([ 10 | new webpack.optimize.UglifyJsPlugin({ 11 | compress: { warnings: false }, 12 | sourceMap: false 13 | }), 14 | new CopyWebpackPlugin([ 15 | { from: './src/ScrollMagnetContainer.vue' }, 16 | { from: './src/ScrollMagnetItem.vue' } 17 | ]), 18 | new webpack.DefinePlugin({ 19 | 'process.env': { 20 | NODE_ENV: '"production"' 21 | } 22 | }) 23 | ]) 24 | 25 | module.exports = config -------------------------------------------------------------------------------- /dist/ScrollMagnetContainer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 179 | 180 | -------------------------------------------------------------------------------- /dist/ScrollMagnetItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 118 | 119 | -------------------------------------------------------------------------------- /dist/vue-scroll-magnet.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-scroll-magnet v0.3.7 3 | * (c) 2017 Todd Beauchamp 4 | * 5 | */ 6 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.VueScrollMagnet=e():t.VueScrollMagnet=e()}(this,function(){return function(t){function e(i){if(n[i])return n[i].exports;var o=n[i]={exports:{},id:i,loaded:!1};return t[i].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";var i=n(1),o=n(8);t.exports={install:function(t){t.component("scroll-magnet-container",i),t.component("scroll-magnet-item",o)},ScrollMagnetContainer:i,ScrollMagnetItem:o}},function(t,e,n){var i,o;n(2),i=n(6);var r=n(7);o=i=i||{},"object"!=typeof i.default&&"function"!=typeof i.default||(o=i=i.default),"function"==typeof o&&(o=o.options),o.render=r.render,o.staticRenderFns=r.staticRenderFns,o._scopeId="data-v-d9583ad8",t.exports=i},function(t,e,n){var i=n(3);"string"==typeof i&&(i=[[t.id,i,""]]);n(5)(i,{});i.locals&&(t.exports=i.locals)},function(t,e,n){e=t.exports=n(4)(),e.push([t.id,".scroll-magnet-container[data-v-d9583ad8]{position:relative}",""])},function(t,e){t.exports=function(){var t=[];return t.toString=function(){for(var t=[],e=0;e=0&&m.splice(e,1)}function a(t){var e=document.createElement("style");return e.type="text/css",r(t,e),e}function c(t,e){var n,i,o;if(e.singleton){var r=v++;n=g||(g=a(e)),i=l.bind(null,n,r,!1),o=l.bind(null,n,r,!0)}else n=a(e),i=d.bind(null,n),o=function(){s(n)};return i(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;i(t=e)}else o()}}function l(t,e,n,i){var o=n?"":i.css;if(t.styleSheet)t.styleSheet.cssText=w(e,o);else{var r=document.createTextNode(o),s=t.childNodes;s[e]&&t.removeChild(s[e]),s.length?t.insertBefore(r,s[e]):t.appendChild(r)}}function d(t,e){var n=e.css,i=e.media,o=e.sourceMap;if(i&&t.setAttribute("media",i),o&&(n+="\n/*# sourceURL="+o.sources[0]+" */",n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */"),t.styleSheet)t.styleSheet.cssText=n;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(n))}}var u={},h=function(t){var e;return function(){return"undefined"==typeof e&&(e=t.apply(this,arguments)),e}},f=h(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),p=h(function(){return document.head||document.getElementsByTagName("head")[0]}),g=null,v=0,m=[];t.exports=function(t,e){e=e||{},"undefined"==typeof e.singleton&&(e.singleton=f()),"undefined"==typeof e.insertAt&&(e.insertAt="bottom");var n=o(t);return i(n,e),function(t){for(var r=[],s=0;s0||e)&&(this.width=this.$el&&this.$el.clientWidth||0),(!this.height>0||n)&&(this.height=this.target&&this.target.clientHeight||0)},getScrollPosition:function(){this.scrollTop=this.getScrollY(),this.offsetTop=this.$el.getBoundingClientRect().top+this.getScrollY()},getScrollY:function(){return"undefined"==typeof window?0:window.scrollY||window.pageYOffset}}}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"scroll-magnet-container",style:"height: "+t.height+"px"},[t._t("default")],2)},staticRenderFns:[]}},function(t,e,n){var i,o;n(9),i=n(11);var r=n(12);o=i=i||{},"object"!=typeof i.default&&"function"!=typeof i.default||(o=i=i.default),"function"==typeof o&&(o=o.options),o.render=r.render,o.staticRenderFns=r.staticRenderFns,o._scopeId="data-v-1c578f50",t.exports=i},function(t,e,n){var i=n(10);"string"==typeof i&&(i=[[t.id,i,""]]);n(5)(i,{});i.locals&&(t.exports=i.locals)},function(t,e,n){e=t.exports=n(4)(),e.push([t.id,".scroll-magnet-item[data-v-1c578f50]{position:relative;width:100%}.is-scrolling[data-v-1c578f50]{position:fixed;top:0}.is-bottomed[data-v-1c578f50]{position:absolute;bottom:0}",""])},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={name:"scroll-magnet-item",data:function(){return{nearestContainer:void 0,width:0,height:0,top:0,scrollDist:0,scrollEnd:0,isBottomed:!1,isScrolling:!1}},props:{offsetTopPad:{type:Number,default:0,required:!1}},created:function(){this.nearestContainer=this.getNearestMagnetContainer()},mounted:function(){var t=this;this.setMagnetHeight(),this.$nextTick(function(){t.setMagnetStatus(t.nearestContainer),t.setMagnetWidth()}),this.$watch("nearestContainer.width",function(){t.width=t.nearestContainer.width}),this.$watch("nearestContainer.scrollTop",function(){t.setMagnetStatus(t.nearestContainer)})},methods:{getNearestMagnetContainer:function(){return this.checkParentForMatch(this.$parent)},checkParentForMatch:function(t){return t&&"scroll-magnet-container"===t.$options._componentTag?t:this.checkParentForMatch(t)},setMagnetWidth:function(){this.width=this.nearestContainer.width},setMagnetHeight:function(){var t=this.$el.getBoundingClientRect();this.height=t.height},setMagnetStatus:function(t){this.setMagnetHeight(),this.scrollDist=t.scrollTop+this.height,this.scrollEnd=t.offsetTop+(t.height-this.offsetTopPad),this.isWithinHeight=this.scrollDist=t.offsetTop&&this.isWithinHeight,this.isBottomed=this.scrollDist>=this.scrollEnd,this.top=this.isBottomed?"auto":this.offsetTopPad+"px"}}}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"scroll-magnet-item",class:{"is-scrolling":t.isScrolling,"is-bottomed":t.isBottomed},style:"width: "+(t.width>0&&t.width)+"px; top: "+t.top},[t._t("default")],2)},staticRenderFns:[]}}])}); -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | db.json 4 | *.log 5 | node_modules/ 6 | .deploy*/ -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Hexo Configuration 2 | ## Docs: https://hexo.io/docs/configuration.html 3 | ## Source: https://github.com/hexojs/hexo/ 4 | 5 | # Site 6 | title: Vue Scroll Magnet 7 | subtitle: Vue Scroll Magnet 8 | description: 9 | author: '@toddlawton' 10 | language: 11 | timezone: 12 | 13 | # URL 14 | ## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/' 15 | url: http://github.com/toddlawton 16 | root: /vue-scroll-magnet/ 17 | permalink: :year/:month/:day/:title/ 18 | permalink_defaults: 19 | 20 | # Directory 21 | source_dir: source 22 | public_dir: public 23 | tag_dir: tags 24 | archive_dir: archives 25 | category_dir: categories 26 | code_dir: downloads/code 27 | i18n_dir: :lang 28 | skip_render: 29 | 30 | # Writing 31 | new_post_name: :title.md # File name of new posts 32 | default_layout: post 33 | titlecase: false # Transform title into titlecase 34 | external_link: true # Open external links in new tab 35 | filename_case: 0 36 | render_drafts: false 37 | post_asset_folder: false 38 | relative_link: false 39 | future: true 40 | highlight: 41 | enable: true 42 | line_number: true 43 | auto_detect: false 44 | tab_replace: 45 | 46 | # Category & Tag 47 | default_category: uncategorized 48 | category_map: 49 | tag_map: 50 | 51 | # Date / Time format 52 | ## Hexo uses Moment.js to parse and display date 53 | ## You can customize the date format as defined in 54 | ## http://momentjs.com/docs/#/displaying/format/ 55 | date_format: YYYY-MM-DD 56 | time_format: HH:mm:ss 57 | 58 | # Pagination 59 | ## Set per_page to 0 to disable pagination 60 | per_page: 10 61 | pagination_dir: page 62 | 63 | # Extensions 64 | ## Plugins: https://hexo.io/plugins/ 65 | ## Themes: https://hexo.io/themes/ 66 | theme: apollo 67 | 68 | archive_generator: 69 | per_page: 0 70 | yearly: false 71 | monthly: false 72 | daily: false 73 | 74 | # Deployment 75 | ## Docs: https://hexo.io/docs/deployment.html 76 | deploy: 77 | type: 78 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-site", 3 | "version": "0.0.0", 4 | "private": true, 5 | "hexo": { 6 | "version": "3.2.2" 7 | }, 8 | "dependencies": { 9 | "hexo": "^3.2.0", 10 | "hexo-browsersync": "^0.2.0", 11 | "hexo-generator-archive": "^0.1.4", 12 | "hexo-generator-category": "^0.1.3", 13 | "hexo-generator-feed": "^1.2.0", 14 | "hexo-generator-index": "^0.2.0", 15 | "hexo-generator-sitemap": "^1.1.2", 16 | "hexo-generator-tag": "^0.2.0", 17 | "hexo-renderer-ejs": "^0.2.0", 18 | "hexo-renderer-jade": "^0.3.0", 19 | "hexo-renderer-marked": "^0.2.10", 20 | "hexo-renderer-stylus": "^0.3.1", 21 | "hexo-server": "^0.2.0" 22 | } 23 | } -------------------------------------------------------------------------------- /docs/public/2016/11/29/a/index.html: -------------------------------------------------------------------------------- 1 | · Vue Scroll Magnet -------------------------------------------------------------------------------- /docs/public/archives/index.html: -------------------------------------------------------------------------------- 1 | Vue Scroll Magnet -------------------------------------------------------------------------------- /docs/public/atom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Vue Scroll Magnet 4 | Vue Scroll Magnet 5 | 6 | 7 | 8 | 2016-11-29T22:15:04.000Z 9 | http://github.com/toddlawton/ 10 | 11 | 12 | @toddlawton 13 | 14 | 15 | 16 | Hexo 17 | 18 | 19 | 20 | 21 | http://github.com/toddlawton/2016/11/29/a/ 22 | 2016-11-29T22:15:03.000Z 23 | 2016-11-29T22:15:04.000Z 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/public/css/apollo.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}::-moz-selection{color:#FFFFFF;background-color:#42b983}::selection{color:#FFFFFF;background-color:#42b983}html,body{width:100%;height:100%}body{margin:0;color:#34495e;font-size:15px;line-height:1.6;background-color:#fff;font-family:'sourcesanspro', 'Helvetica Neue', Arial, sans-serif}ul.nav,ul.post-list{margin:0;padding:0;list-style-type:none}ul{margin:1rem 0}a,a:active{color:#2c3e50;text-decoration:none}a.nav-list-link.active,a.nav-list-link:hover,a.post-title-link:hover{border-bottom:2px solid #42b983}hr{border:0}code{margin:0 2px;padding:3px 5px;color:#e96900;border-radius:2px;white-space:inherit}iframe,video{max-width:100%;margin:1rem auto;display:block}table{width:100%;margin:1em auto}table thead{background-color:#ddd}table thead th{padding:5px;min-width:20px}table tbody tr:nth-child(2n){background-color:#eee}table tbody td{padding:5px;vertical-align:text-top}header{min-height:60px}header .logo-link{float:left}header .nav{float:right;left:80px}header .logo-link img{height:60px}header .nav-list-item{display:inline-block;padding:19px 10px}header .nav-list-item a{font-size:16px;line-height:1.4}.home.post-list{margin:2em 0}.home.post-list .post-list-item{padding:1em 0 2em;border-bottom:1px solid #ddd}.home.post-list .post-list-item:last-child{border-bottom:0px}.home.post-list .post-content h2:before,.home.post-list .post-content h3:before,.home.post-list .post-content h4:before,.home.post-list .post-content h5:before,.home.post-list .post-content h6:before{content:''}.home.post-list .post-content>ul{list-style:initial}.home.post-list .read-more{color:#42b983}.archive{max-width:500px;margin:5em auto}.archive .post-item{padding:2px 0 0 50px}.archive .post-time,.archive .post-title-link{font-size:1rem}.archive .post-title-link{display:block;margin-left:125px;color:#42b983;word-break:break-all}.archive .post-title-link:hover{border-bottom:0;color:#267B54}.archive .post-info{float:left;width:125px;color:#7f8c8d}.post{padding-top:1em}.post-block .post-title{margin:0.65em 0;color:#2c3e50;font-size:1.5em}.post-block .post-info{color:#7f8c8d;margin:1.2em 0}.post-block .post-info span{margin-left:0.5rem}.post-block .post-info a.post-from{margin-left:0.5rem;padding:3px 6px;border-radius:5px;font-size:12px;color:white;background-color:#E36B6B}.post-content h2,.post-content h3,.post-content h4,.post-content h5,.post-content h6{position:relative;margin:1em 0}.post-content h2:before,.post-content h3:before,.post-content h4:before,.post-content h5:before,.post-content h6:before{content:"#";color:#42b983;position:absolute;left:-0.7em;top:-4px;font-size:1.2em;font-weight:bold}.post-content h4:before,.post-content h5:before,.post-content h6:before{content:""}.post-content h2,.post-content h3{font-size:22px}.post-content h4,.post-content h5,.post-content h6{font-size:18px}.post-content a{color:#42b983;word-break:break-all}.post-content blockquote{margin:2em 0;padding-left:20px;border-left:4px solid #42b983}.post-content img{display:block;max-width:100%;margin:1em auto}.post-content>table,.post-content>figure.highlight{box-shadow:0 1px 2px rgba(0,0,0,0.125)}.post-content .tip{position:relative;margin:2em 0;padding:12px 24px 12px 30px;border-left:4px solid #f66;border-top-right-radius:2px;border-bottom-right-radius:2px;background-color:#f8f8f8}.post-content .tip br{display:none}.post-content .tip:before{position:absolute;top:14px;left:-12px;content:"!";width:20px;height:20px;border-radius:100%;color:#fff;font-size:14px;line-height:20px;font-weight:bold;text-align:center;background-color:#f66;font-family:'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif}#mask{position:fixed;overflow:scroll;width:100%;height:100%;padding:1em 0;background-color:rgba(0,0,0,0.5);z-index:10}#mask #mask-image{max-width:95%}code,pre{font-size:0.8em;background-color:#f8f8f8;font-family:'Roboto Mono', Monaco, courier, monospace}.highlight{position:relative;margin:1em 0;border-radius:2px;line-height:1.1em;background-color:#f8f8f8;overflow-x:auto}.highlight table,.highlight tr,.highlight td{width:100%;border-collapse:collapse;padding:0;margin:0}.highlight .gutter{display:none}.highlight .code pre{padding:1.2em 1.4em;line-height:1.5em;margin:0}.highlight .code pre .line{width:auto;height:18px}.highlight.html .code:after,.highlight.js .code:after,.highlight.bash .code:after,.highlight.css .code:after,.highlight.scss .code:after,.highlight.diff .code:after,.highlight.java .code:after,.highlight.xml .code:after,.highlight.python .code:after,.highlight.json .code:after,.highlight.swift .code:after,.highlight.ruby .code:after,.highlight.perl .code:after,.highlight.php .code:after,.highlight.c .code:after,.highlight.cpp .code:after,.highlight.ts .code:after{position:absolute;top:0;right:0;color:#ccc;text-align:right;font-size:0.75em;padding:5px 10px 0;line-height:15px;height:15px;font-weight:600}.highlight.html .code:after{content:"HTML"}.highlight.js .code:after{content:"JS"}.highlight.bash .code:after{content:"BASH"}.highlight.css .code:after{content:"CSS"}.highlight.scss .code:after{content:"SCSS"}.highlight.diff .code:after{content:"DIFF"}.highlight.java .code:after{content:"JAVA"}.highlight.xml .code:after{content:"XML"}.highlight.python .code:after{content:"PYTHON"}.highlight.json .code:after{content:"JSON"}.highlight.swift .code:after{content:"SWIFT"}.highlight.ruby .code:after{content:"RUBY"}.highlight.perl .code:after{content:"PERL"}.highlight.php .code:after{content:"PHP"}.highlight.c .code:after{content:"C"}.highlight.java .code:after{content:"JAVA"}.highlight.cpp .code:after{content:"CPP"}.highlight.ts .code:after{content:"TS"}.highlight.cpp .code:after{content:'C++'}pre{color:#525252}pre .function .keyword,pre .constant{color:#0092db}pre .keyword,pre .attribute{color:#e96900}pre .number,pre .literal{color:#ae81ff}pre .tag,pre .tag .title,pre .change,pre .winutils,pre .flow,pre .lisp .title,pre .clojure .built_in,pre .nginx .title,pre .tex .special{color:#2973b7}pre .symbol,pre .symbol .string,pre .value,pre .regexp{color:#42b983}pre .title{color:#83B917}pre .tag .value,pre .string,pre .subst,pre .haskell .type,pre .preprocessor,pre .ruby .class .parent,pre .built_in,pre .sql .aggregate,pre .django .template_tag,pre .django .variable,pre .smalltalk .class,pre .javadoc,pre .django .filter .argument,pre .smalltalk .localvars,pre .smalltalk .array,pre .attr_selector,pre .pseudo,pre .addition,pre .stream,pre .envvar,pre .apache .tag,pre .apache .cbracket,pre .tex .command,pre .prompt{color:#42b983}pre .comment,pre .java .annotation,pre .python .decorator,pre .template_comment,pre .pi,pre .doctype,pre .shebang,pre .apache .sqbracket,pre .tex .formula{color:#b3b3b3}pre .deletion{color:#BA4545}pre .coffeescript .javascript,pre .javascript .xml,pre .tex .formula,pre .xml .javascript,pre .xml .vbscript,pre .xml .css,pre .xml .cdata{opacity:0.5}.paginator{margin:4em 0;text-align:center}.paginator .prev,.paginator .next{display:inline-block;margin:0 4px;padding:4px 12px;border-radius:4px;border-bottom:4px solid #3aa373;font-size:14px;color:#fff;background-color:#4fc08d}.paginator .prev:hover,.paginator .next:hover{background-color:#22bd77}.ds-thread,#disqus_thread{margin-bottom:2em}section.container{margin:2em 10px}@media screen and (min-width: 700px){.wrap{width:700px;margin:0 auto}header{padding:20px 10px}}@media screen and (max-width: 700px){.wrap{width:100%}header{padding:20px 0}header a.logo-link,header ul.nav.nav-list{float:none;display:block;text-align:center}header li.nav-list-item{padding:10px 10px}section.container,.home.post-list,.archive{margin-top:0}.archive .post-item{padding-left:20px}.post-content h2,.post-content h3,.post-content h4,.post-content h5,.post-content h6{max-width:300px;left:15px}.ds-thread,#disqus_thread{margin:2em 10px}}footer{padding-bottom:1px}footer .copyright{margin:4em 0;border-top:1px solid #ddd;text-align:center}footer .copyright p,footer .copyright a{color:#aaa;font-size:14px;font-weight:100}footer .copyright a:hover{color:#888}@font-face{font-family:'sourcesanspro';src:url("/vue-scroll-magnet/font/sourcesanspro.woff2") format("woff2"),url("/vue-scroll-magnet/font/sourcesanspro.woff") format("woff");font-weight:normal;font-style:normal} 2 | -------------------------------------------------------------------------------- /docs/public/docs/index.html: -------------------------------------------------------------------------------- 1 | Docs · Vue Scroll Magnet

Vue Scroll Magnet

Documentation

What is Vue Scroll Magnet?

Vue Scroll Magnet is a pair of component wrappers which allow elements to follow the viewport while scrolling and then anchor at the upper and lower extremeties of their parent container. This module is inspired by a jQuery plugin called Sticky kit.

2 |

Installation

3 |

This component relies on ES6 and may not be compatible with Vue 1.x projects.

4 |
5 |
1
npm install -S vue-scroll-magnet
6 |

Usage (Global)

Install the components globally:

1
2
3
4
import Vue from 'vue';
import VueScrollMagnet from 'vue-scroll-magnet';
Vue.use(VueScrollMagnet);

7 |

Usage (Component)

Include the wrappers into your component using import:

1
2
3
4
5
6
7
8
9
10
11
12
/* inside your Vue component's script tag */
import { ScrollMagnetContainer, ScrollMagnetItem } from 'vue-scroll-magnet';
export default {
...
components: {
ScrollMagnetContainer,
ScrollMagnetItem
}
...
};

8 |

HTML structure

The scrollable height boundary will be determined as the direct parent of the <scroll-magnet-container> by default, however it can be configured using an element selector which it can use as a fixed context.

9 |

In the example below, <scroll-magnet-container> will automatically use the top of #app as its upper boundary and its height of 500px as its lower boundary unless a specific element context is provided.

10 |
1
2
3
4
5
<div id="app" style="height: 500px;">
<scroll-magnet-container>
<scroll-magnet-item>This content will scroll</scroll-magnet-item>
</scroll-magnet-container>
</div>
11 |

Tests

To run unit tests & code coverage:

1
npm test

12 |
-------------------------------------------------------------------------------- /docs/public/examples/index.html: -------------------------------------------------------------------------------- 1 | Examples · Vue Scroll Magnet

Vue Scroll Magnet

Examples

Basic example

1
2
3
4
5
6
7
8
9
10
<div class="example-container">
<scroll-magnet-container>
<scroll-magnet-item>
<div class="example-sticky-item">
<div class="text">This item will stick as you scroll!</div>
</div>
</scroll-magnet-item>
</scroll-magnet-container>
<!-- Placeholder paragraphs -->
</div>
2 |
1
2
3
4
5
6
.example-container {
min-height: 600px;
}
.example-item {
min-height: 200px;
}
3 | 36 | 37 |
38 | 39 | 40 | 41 |
-------------------------------------------------------------------------------- /docs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSENSE/vue-scroll-magnet/3b938ccd37ba2453cd088d8a91c242136f699a57/docs/public/favicon.png -------------------------------------------------------------------------------- /docs/public/font/sourcesanspro.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSENSE/vue-scroll-magnet/3b938ccd37ba2453cd088d8a91c242136f699a57/docs/public/font/sourcesanspro.woff -------------------------------------------------------------------------------- /docs/public/font/sourcesanspro.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSENSE/vue-scroll-magnet/3b938ccd37ba2453cd088d8a91c242136f699a57/docs/public/font/sourcesanspro.woff2 -------------------------------------------------------------------------------- /docs/public/index.html: -------------------------------------------------------------------------------- 1 | Vue Scroll Magnet -------------------------------------------------------------------------------- /docs/public/scss/apollo.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @import "_partial/normalize"; 4 | @import "_partial/base"; 5 | @import "_partial/header"; 6 | @import "_partial/home-post-list"; 7 | @import "_partial/archive-post-list"; 8 | @import "_partial/post"; 9 | @import "_partial/footer"; 10 | @import "_partial/mq"; 11 | @import "_partial/copyright"; 12 | 13 | @font-face { 14 | font-family: 'sourcesanspro'; 15 | src: url('/vue-scroll-magnet/font/sourcesanspro.woff2') format('woff2'), 16 | url('/vue-scroll-magnet/font/sourcesanspro.woff') format('woff'); 17 | font-weight: normal; 18 | font-style: normal; 19 | } 20 | -------------------------------------------------------------------------------- /docs/public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http://github.com/toddlawton/examples/index.html 6 | 7 | 2016-11-29T23:45:59.000Z 8 | 9 | 10 | 11 | 12 | http://github.com/toddlawton/docs/index.html 13 | 14 | 2016-11-29T23:40:17.000Z 15 | 16 | 17 | 18 | 19 | http://github.com/toddlawton/vue-scroll-magnet/2016/11/29/a/ 20 | 21 | 2016-11-29T22:15:04.000Z 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/scaffolds/draft.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | tags: 4 | --- 5 | -------------------------------------------------------------------------------- /docs/scaffolds/page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | --- 4 | -------------------------------------------------------------------------------- /docs/scaffolds/post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | tags: 4 | --- 5 | -------------------------------------------------------------------------------- /docs/source/_posts/a.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSENSE/vue-scroll-magnet/3b938ccd37ba2453cd088d8a91c242136f699a57/docs/source/_posts/a.md -------------------------------------------------------------------------------- /docs/source/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Docs 3 | --- 4 | 5 | # Documentation 6 | 7 | ## What is Vue Scroll Magnet? 8 | 9 | Vue Scroll Magnet is a pair of component wrappers which allow elements to follow the viewport while scrolling and then anchor at the upper and lower extremeties of their parent container. This module is inspired by a jQuery plugin called Sticky kit. 10 | 11 | ## Installation 12 | 13 | > This component relies on ES6 and may not be compatible with Vue 1.x projects. 14 | 15 | ``` bash 16 | npm install -S vue-scroll-magnet 17 | ``` 18 | 19 | ## Usage (Global) 20 | Install the components globally: 21 | ``` js 22 | import Vue from 'vue'; 23 | import VueScrollMagnet from 'vue-scroll-magnet'; 24 | 25 | Vue.use(VueScrollMagnet); 26 | ``` 27 | 28 | ## Usage (Component) 29 | Include the wrappers into your component using import: 30 | ``` js 31 | /* inside your Vue component's script tag */ 32 | 33 | import { ScrollMagnetContainer, ScrollMagnetItem } from 'vue-scroll-magnet'; 34 | 35 | export default { 36 | ... 37 | components: { 38 | ScrollMagnetContainer, 39 | ScrollMagnetItem 40 | } 41 | ... 42 | }; 43 | ``` 44 | 45 | ### HTML structure 46 | The scrollable height boundary will be determined as the direct parent of the <scroll-magnet-container> by default, however it can be configured using an element selector which it can use as a fixed context. 47 | 48 | In the example below, <scroll-magnet-container> will automatically use the top of #app as its upper boundary and its height of 500px as its lower boundary unless a specific element context is provided. 49 | 50 | ``` html 51 |
52 | 53 | This content will scroll 54 | 55 |
56 | ``` 57 | 58 | ### Tests 59 | To run unit tests & code coverage: 60 | ``` bash 61 | npm test 62 | ``` -------------------------------------------------------------------------------- /docs/source/examples/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Examples 3 | --- 4 | 5 | # Examples 6 | 7 | ## Basic example 8 | 9 | ``` html 10 |
11 | 12 | 13 |
14 |
This item will stick as you scroll!
15 |
16 |
17 |
18 | 19 |
20 | ``` 21 | 22 | ``` css 23 | .example-container { 24 | min-height: 600px; 25 | } 26 | .example-item { 27 | min-height: 200px; 28 | } 29 | ``` 30 | 31 | 64 | 65 |
66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Scroll Magnet 6 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-scroll-magnet", 3 | "version": "0.3.7", 4 | "description": "Wrappers for Vue components which follow the viewport while scrolling within the bounds of their parent container", 5 | "author": "Todd Beauchamp ", 6 | "main": "src/index.js", 7 | "scripts": { 8 | "build": "webpack --config build/webpack.min.conf.js", 9 | "unit": "./node_modules/.bin/karma start test/unit/karma.conf.js --single-run", 10 | "test": "npm run unit", 11 | "lint": "eslint --ext .js,.vue src test/unit/specs" 12 | }, 13 | "devDependencies": { 14 | "autoprefixer": "^6.4.0", 15 | "babel-core": "^6.0.0", 16 | "babel-eslint": "^7.1.1", 17 | "babel-loader": "^6.0.0", 18 | "babel-plugin-transform-runtime": "^6.0.0", 19 | "babel-preset-es2015": "^6.0.0", 20 | "babel-preset-stage-2": "^6.0.0", 21 | "babel-register": "^6.0.0", 22 | "chai": "^3.5.0", 23 | "chai-jquery": "^2.0.0", 24 | "chalk": "^1.1.3", 25 | "connect-history-api-fallback": "^1.1.0", 26 | "copy-webpack-plugin": "^4.0.1", 27 | "css-loader": "^0.25.0", 28 | "eslint": "^3.13.1", 29 | "eslint-config-airbnb-base": "^8.0.0", 30 | "eslint-config-ssense": "^0.1.0", 31 | "eslint-friendly-formatter": "^2.0.5", 32 | "eslint-import-resolver-webpack": "^0.6.0", 33 | "eslint-loader": "^1.5.0", 34 | "eslint-plugin-fp": "^2.3.0", 35 | "eslint-plugin-html": "^1.3.0", 36 | "eslint-plugin-import": "^1.16.0", 37 | "eslint-plugin-vue": "^1.0.0", 38 | "eventsource-polyfill": "^0.9.6", 39 | "express": "^4.13.3", 40 | "extract-text-webpack-plugin": "^1.0.1", 41 | "file-loader": "^0.9.0", 42 | "function-bind": "^1.0.2", 43 | "html-webpack-plugin": "^2.8.1", 44 | "http-proxy-middleware": "^0.17.2", 45 | "inject-loader": "^2.0.1", 46 | "isparta-loader": "^2.0.0", 47 | "jquery": "^3.1.1", 48 | "json-loader": "^0.5.4", 49 | "karma": "^1.3.0", 50 | "karma-chrome-launcher": "^2.0.0", 51 | "karma-coverage": "^1.1.1", 52 | "karma-jquery": "^0.1.0", 53 | "karma-mocha": "^1.2.0", 54 | "karma-phantomjs-launcher": "^1.0.0", 55 | "karma-sinon-chai": "^1.2.0", 56 | "karma-sourcemap-loader": "^0.3.7", 57 | "karma-spec-reporter": "0.0.26", 58 | "karma-verbose-reporter": "0.0.3", 59 | "karma-webpack": "^1.7.0", 60 | "lolex": "^1.4.0", 61 | "mocha": "^3.1.0", 62 | "opn": "^4.0.2", 63 | "ora": "^0.3.0", 64 | "phantomjs-prebuilt": "^2.1.3", 65 | "semver": "^5.3.0", 66 | "shelljs": "^0.7.4", 67 | "sinon": "^1.17.3", 68 | "sinon-chai": "^2.8.0", 69 | "url-loader": "^0.5.7", 70 | "vue": "^2.0.1", 71 | "vue-loader": "^9.4.0", 72 | "vue-router": "^2.0.3", 73 | "vue-style-loader": "^1.0.0", 74 | "webpack": "^1.13.2", 75 | "webpack-dev-middleware": "^1.8.3", 76 | "webpack-hot-middleware": "^2.12.2", 77 | "webpack-merge": "^0.14.1" 78 | }, 79 | "engines": { 80 | "node": ">= 4.0.0", 81 | "npm": ">= 3.0.0" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ScrollMagnetContainer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 179 | 180 | -------------------------------------------------------------------------------- /src/ScrollMagnetItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 118 | 119 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | var ScrollMagnetContainer = require('./ScrollMagnetContainer.vue'), 4 | ScrollMagnetItem = require('./ScrollMagnetItem.vue'); 5 | 6 | module.exports = { 7 | install: function(Vue) { 8 | Vue.component('scroll-magnet-container', ScrollMagnetContainer); 9 | Vue.component('scroll-magnet-item', ScrollMagnetItem); 10 | }, 11 | ScrollMagnetContainer: ScrollMagnetContainer, 12 | ScrollMagnetItem: ScrollMagnetItem 13 | }; 14 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | // Polyfill fn.bind() for PhantomJS 2 | /* eslint-disable no-extend-native */ 3 | Function.prototype.bind = require('function-bind') 4 | 5 | // require all test files (files that ends with .spec.js) 6 | var testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | var srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var path = require('path') 7 | var merge = require('webpack-merge') 8 | var baseConfig = require('../../build/webpack.base.conf.js') 9 | //var utils = require('../../build/utils') 10 | var projectRoot = path.resolve(__dirname, '../../') 11 | 12 | var webpackConfig = merge(baseConfig, { 13 | // use inline sourcemap for karma-sourcemap-loader 14 | /*module: { 15 | loaders: utils.styleLoaders() 16 | },*/ 17 | devtool: '#inline-source-map', 18 | vue: { 19 | loaders: { 20 | js: 'isparta' 21 | } 22 | } 23 | }) 24 | 25 | // no need for app entry during tests 26 | delete webpackConfig.entry 27 | 28 | // make sure isparta loader is applied before eslint 29 | webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || [] 30 | webpackConfig.module.preLoaders.unshift({ 31 | test: /\.js$/, 32 | loader: 'isparta', 33 | include: projectRoot, 34 | exclude: /test\/unit|node_modules/ 35 | }) 36 | 37 | // only apply babel for test files when using isparta 38 | webpackConfig.module.loaders.some(function (loader, i) { 39 | if (loader.loader === 'babel') { 40 | loader.include = /test\/unit/ 41 | return true 42 | } 43 | }) 44 | 45 | module.exports = function (config) { 46 | config.set({ 47 | // to run in additional browsers: 48 | // 1. install corresponding karma launcher 49 | // http://karma-runner.github.io/0.13/config/browsers.html 50 | // 2. add it to the `browsers` array below. 51 | browsers: ['Chrome'], 52 | frameworks: ['mocha', 'sinon-chai'], 53 | reporters: ['spec', 'coverage'], 54 | files: ['./index.js'], 55 | preprocessors: { 56 | './index.js': ['webpack', 'sourcemap'] 57 | }, 58 | logLevel: config.LOG_DEBUG, 59 | webpack: webpackConfig, 60 | webpackMiddleware: { 61 | noInfo: true 62 | }, 63 | coverageReporter: { 64 | dir: './coverage', 65 | reporters: [ 66 | { type: 'lcov', subdir: '.' }, 67 | { type: 'text-summary' } 68 | ] 69 | } 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /test/unit/specs/ScrollMagnetContainer.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import Vue from 'vue'; 4 | import VueScrollMagnet from '../../../src/index.js'; 5 | import ScrollMagnetContainer from '../../../src/ScrollMagnetContainer.vue'; 6 | import { createAppContainer, simulateScroll } from '../utils'; 7 | 8 | simulateScroll(0); // Reset scroll position 9 | 10 | describe('ScrollMagnetContainer.vue', () => { 11 | it('should install successfully when used globally', () => { 12 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 13 | createAppContainer(); 14 | Vue.use(VueScrollMagnet); 15 | const vm = new Vue({ 16 | el: '#appInner', 17 | template: '', 18 | }); 19 | vm.$mount; 20 | expect(vm.$children[0]._isMounted).to.equal(true); 21 | }); 22 | 23 | it('should mount successfully', () => { 24 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 25 | createAppContainer(); 26 | Vue.component('scroll-magnet-container', ContainerInstance); 27 | const vm = new Vue({ 28 | el: '#appInner', 29 | template: '', 30 | }); 31 | vm.$mount; 32 | expect(vm.$children[0]._isMounted).to.equal(true); 33 | }); 34 | 35 | it('should match an element when targetElement prop is set', (done) => { 36 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 37 | createAppContainer(); 38 | Vue.component('scroll-magnet-container', ContainerInstance); 39 | const vm = new Vue({ 40 | el: '#appInner', 41 | template: '' 42 | }); 43 | vm.$mount; 44 | expect(vm.$children[0].target).to.not.equal(undefined); 45 | expect(vm.$children[0].target.id).to.equal('app'); 46 | done(); 47 | }); 48 | 49 | it('should update scrollTop attribute when scroll occurs', (done) => { 50 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 51 | createAppContainer(); 52 | Vue.component('scroll-magnet-container', ContainerInstance); 53 | const vm = new Vue({ 54 | el: '#appInner', 55 | template: '' 56 | }); 57 | vm.$mount; 58 | simulateScroll(600); 59 | expect(vm.$children[0].scrollTop).to.equal(600); 60 | done(); 61 | }); 62 | 63 | it('should not attach listeners if window is not defined', (done) => { 64 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 65 | createAppContainer(); 66 | Vue.component('scroll-magnet-container', ContainerInstance); 67 | const vm = new Vue({ 68 | el: '#appInner', 69 | template: '' 70 | }); 71 | vm.$mount; 72 | simulateScroll(600); 73 | expect(vm.$children[0].scrollTop).to.equal(600); 74 | done(); 75 | }); 76 | 77 | it('should unmount successfully', () => { 78 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 79 | createAppContainer(); 80 | Vue.component('scroll-magnet-container', ContainerInstance); 81 | const vm = new Vue({ 82 | el: '#appInner', 83 | template: '' 84 | }); 85 | vm.$mount; 86 | vm.$destroy(); 87 | expect(vm.$children[0]._isDestroyed).to.equal(true); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/unit/specs/ScrollMagnetItem.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import Vue from 'vue'; 4 | import ScrollMagnetContainer from '../../../src/ScrollMagnetContainer'; 5 | import ScrollMagnetItem from '../../../src/ScrollMagnetItem'; 6 | import { createAppContainer, simulateScroll } from '../utils'; 7 | 8 | describe('ScrollMagnetItem.vue', () => { 9 | it('should mount successfully', () => { 10 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 11 | const ItemInstance = Vue.extend(ScrollMagnetItem); 12 | const vm = new ContainerInstance(); 13 | vm.$children.push(ItemInstance); 14 | vm.$mount(); 15 | expect(vm._isMounted).to.equal(true); 16 | }); 17 | 18 | it('should update default data attributes to match parent container context', (done) => { 19 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 20 | const ItemInstance = Vue.extend(ScrollMagnetItem); 21 | const appContainer = document.createElement('div'); 22 | createAppContainer(); 23 | Vue.component('scroll-magnet-container', ContainerInstance); 24 | Vue.component('scroll-magnet-item', ItemInstance); 25 | const vm = new Vue({ 26 | el: '#appInner', 27 | template: '' 28 | }); 29 | vm.$mount(); 30 | const $container = vm.$children[0]; 31 | const $item = $container.$children[0]; 32 | setTimeout(() => { 33 | // Data attributes 34 | expect($item.nearestContainer).to.equal(vm.$children[0]); 35 | expect($item.width).to.equal($item.nearestContainer.$el.clientWidth); 36 | expect($item.height).to.equal(0); 37 | expect($item.scrollDist).to.equal(600); 38 | expect($item.scrollEnd).to.equal(($item.nearestContainer.offsetTop + ($item.nearestContainer.height - $item.offsetTopPad))); 39 | expect($item.isBottomed).to.equal(false); 40 | expect($item.isScrolling).to.equal(false); 41 | // Props 42 | expect($item.offsetTopPad).to.equal(0); 43 | done(); 44 | }, 100); 45 | }); 46 | 47 | it('should use default top value if not isBottomed', (done) => { 48 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 49 | const ItemInstance = Vue.extend(ScrollMagnetItem); 50 | createAppContainer(); 51 | Vue.component('scroll-magnet-container', ContainerInstance); 52 | Vue.component('scroll-magnet-item', ItemInstance); 53 | const vm = new Vue({ 54 | el: '#appInner', 55 | template: '' 56 | }); 57 | vm.$mount(); 58 | const $container = vm.$children[0]; 59 | const $item = $container.$children[0]; 60 | Vue.set(vm.$children[0], 'scrollTop', 0); 61 | setTimeout(() => { 62 | expect($item.top).to.not.equal('initial'); 63 | done(); 64 | }, 100); 65 | }); 66 | 67 | it('should unmount successfully', () => { 68 | const ContainerInstance = Vue.extend(ScrollMagnetContainer); 69 | const ItemInstance = Vue.extend(ScrollMagnetItem); 70 | const vm = new ContainerInstance(); 71 | vm.$children.push(ItemInstance); 72 | vm.$mount(); 73 | vm.$destroy(); 74 | expect(vm._isDestroyed).to.equal(true); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/unit/utils/index.js: -------------------------------------------------------------------------------- 1 | var simulateScroll = function(distance) { 2 | var e = new CustomEvent('scroll') 3 | document.body.style.minHeight = '10000px'; 4 | window.pageYOffset = distance; 5 | window.dispatchEvent(e); 6 | } 7 | 8 | var createAppContainer = function() { 9 | // Remove existing app nodes 10 | var element = document.getElementById('app'); 11 | if (element) { 12 | element.parentNode.removeChild(element); 13 | } 14 | const appContainer = document.createElement('div'); 15 | const innerContainer = document.createElement('div'); 16 | appContainer.id = 'app'; 17 | innerContainer.id = 'appInner'; 18 | appContainer.style.height = '800px'; 19 | appContainer.appendChild(innerContainer); 20 | document.body.appendChild(appContainer); 21 | } 22 | 23 | module.exports = { 24 | 'simulateScroll': simulateScroll, 25 | 'createAppContainer': createAppContainer 26 | }; 27 | --------------------------------------------------------------------------------