├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── babel.config.js ├── docs └── README.md ├── examples ├── App.vue ├── Item.vue ├── Test.vue ├── api.js ├── components │ └── SimpleHeader.vue ├── iconfont.css ├── icons.png ├── icons2.png ├── item-factory.js ├── main.js ├── pages │ ├── animated.vue │ ├── carousel.vue │ ├── index.vue │ ├── list.vue │ ├── mobile.vue │ ├── simple.vue │ ├── swipe.vue │ └── usage.vue ├── router.js └── store.js ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── affix.js ├── slide.js ├── swipe.js ├── utils.js └── v-switcher.vue ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ['plugin:vue/recommended', '@vue/prettier'], 7 | rules: { 8 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 9 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 10 | }, 11 | parserOptions: { 12 | parser: 'babel-eslint' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-present, Yuxi (Evan) You 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # v-switcher 2 | 3 | ### usage 4 | ```sh 5 | yarn add v-switcher 6 | // or 7 | npm i v-switcher 8 | ``` 9 | 10 | ```javascript 11 | import Vue from 'vue' 12 | import VSwitcher from 'v-switcher' 13 | import 'v-switcher/dist/v-switcher.css' 14 | 15 | Vue.component(VSwitcher.name, VSwitcher) 16 | ``` 17 | 18 | ### props 19 | | name | type | default | required | description | 20 | | --- | --- | --- | --- | --- | 21 | | headers | Array | [] | Y | tab-header 的数组,支持 icon | 22 | | default-index | Number | 0 | N | 默认选中的 tab index | 23 | | routable | Boolean | false | N | 设为 true 则为路由模式 | 24 | | animated | Boolean | false | N | 是否支持切换动画 | 25 | | duration | Number | 300 | N | 切换动画的时长,ms | 26 | | align | String | start | N | tab 的展示模式 ['around', 'start', 'center', 'end', 'vertical'] | 27 | | swipe | Boolean | false | N | 是否支持左右手势滑动 | 28 | | headerTrigger | String | click | N | 头部动画触发的方式 ['click', 'hover'] | 29 | | anchorTrigger | String | click | N | 锚点动画触发的方式 ['click', 'hover'] | 30 | | anchorPadding | Number | 0 | N | 锚点元素的 padding 值 | 31 | | autoplay | Number | 0 | N | 自动切换的时长(ms)默认不自动切换 | 32 | | contentWidth | String | 100% | N | 每个 content 的宽度,默认 100% | 33 | | headerHeight | Number | 40 | N | 每个 item 的高度(px),默认 40 | 34 | | fixedTop | Number | undefined | N | 如果设值,就为 headers fixed 时距离顶部的高度 | 35 | | sticky | Boolean | false | N | 是否使用 100% 高度布局 | 36 | | disabledSwipe | Boolean | false | N | 是否禁止 swipe 的 touch 事件 | 37 | | continuousSwipe | Boolean | false | N | 当使用 swipe 的时候,是否是无限滚动模式,如果 autoplay > 0 则强制为 true | 38 | 39 | ### example 40 | > 用例比较多,比较复杂,等之后有时间了补在线 demo,现在先把项目 clone 到本地 npm run dev 查看吧 41 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@vue/app', 5 | { 6 | useBuiltIns: false 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Hello VuePress! 2 | -------------------------------------------------------------------------------- /examples/App.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | 124 | 125 | 168 | -------------------------------------------------------------------------------- /examples/Item.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 28 | 29 | 57 | -------------------------------------------------------------------------------- /examples/Test.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 25 | -------------------------------------------------------------------------------- /examples/api.js: -------------------------------------------------------------------------------- 1 | import ItemFactory from './item-factory' 2 | 3 | export const getListByPage = ({ page, count, index }) => { 4 | console.log('getListByPage', index) 5 | return new Promise(resolve => { 6 | const total = 1024 7 | const hasFetch = (page - 1) * count 8 | const getLength = total - hasFetch >= count ? count : total - hasFetch 9 | const no_more = getLength + hasFetch >= total 10 | setTimeout(() => { 11 | const result = ItemFactory.get(getLength) 12 | resolve({ 13 | result, 14 | no_more, 15 | total 16 | }) 17 | }, 500) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /examples/components/SimpleHeader.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /examples/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2') 3 | } 4 | 5 | .iconfont { 6 | font-family: "iconfont" !important; 7 | font-size: 16px; 8 | font-style: normal; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | .ic-qq_connect:before { 14 | content: "\e603"; 15 | } 16 | 17 | .ic-douban:before { 18 | content: "\e601"; 19 | } 20 | 21 | .ic-close:before { 22 | content: "\e631"; 23 | } 24 | 25 | .ic-catalog:before { 26 | content: "\e694"; 27 | } 28 | 29 | .ic-arrow-pulldown:before { 30 | content: "\e64d"; 31 | } 32 | 33 | .ic-google_oauth2:before { 34 | content: "\e600"; 35 | } 36 | 37 | .ic-tiaozhuan:before { 38 | content: "\e6a7"; 39 | } 40 | 41 | .ic-wechat:before { 42 | content: "\e604"; 43 | } 44 | 45 | .ic-weibo:before { 46 | content: "\e605"; 47 | } 48 | 49 | .ic-signed-author:before { 50 | content: "\e60e"; 51 | } 52 | 53 | .ic-mode-night:before { 54 | content: "\e64e"; 55 | } 56 | 57 | .ic-notification-subscribed:before { 58 | content: "\e630"; 59 | } 60 | 61 | .ic-notification-fail:before { 62 | content: "\e65b"; 63 | } 64 | 65 | .ic-notification-addcollection:before { 66 | content: "\e65c"; 67 | } 68 | 69 | .ic-user:before { 70 | content: "\e65e"; 71 | } 72 | 73 | .ic-list-comments:before { 74 | content: "\e661"; 75 | } 76 | 77 | .ic-list-like:before { 78 | content: "\e662"; 79 | } 80 | 81 | .ic-list-read:before { 82 | content: "\e664"; 83 | } 84 | 85 | .ic-followed:before { 86 | content: "\e610"; 87 | } 88 | 89 | .ic-follow:before { 90 | content: "\e611"; 91 | } 92 | 93 | .ic-friends:before { 94 | content: "\e617"; 95 | } 96 | 97 | .ic-woman:before { 98 | content: "\e645"; 99 | } 100 | 101 | .ic-man:before { 102 | content: "\e646"; 103 | } 104 | 105 | .ic-password:before { 106 | content: "\e614"; 107 | } 108 | 109 | .ic-ios:before { 110 | content: "\e612"; 111 | } 112 | 113 | .ic-error:before { 114 | content: "\e648"; 115 | } 116 | 117 | .ic-android:before { 118 | content: "\e65f"; 119 | } 120 | 121 | .ic-verify:before { 122 | content: "\e61f"; 123 | } 124 | 125 | .ic-show:before { 126 | content: "\e621"; 127 | } 128 | 129 | .ic-hide:before { 130 | content: "\e622"; 131 | } 132 | 133 | .ic-link:before { 134 | content: "\e616"; 135 | } 136 | 137 | .ic-more:before { 138 | content: "\e620"; 139 | } 140 | 141 | .ic-appdownload:before { 142 | content: "\e69d"; 143 | } 144 | 145 | .ic-selected:before { 146 | content: "\e69e"; 147 | } 148 | 149 | .ic-openinapp:before { 150 | content: "\e6a5"; 151 | } 152 | 153 | .ic-back:before { 154 | content: "\e6a6"; 155 | } 156 | 157 | .ic-write:before { 158 | content: "\e6aa"; 159 | } 160 | 161 | .ic-question:before { 162 | content: "\e613"; 163 | } 164 | 165 | .ic-zan-active:before { 166 | content: "\e6bd"; 167 | } 168 | 169 | .ic-comment:before { 170 | content: "\e6be"; 171 | } 172 | 173 | .ic-zan:before { 174 | content: "\e6bf"; 175 | } 176 | 177 | .ic-like-active:before { 178 | content: "\e6c7"; 179 | } 180 | 181 | .ic-like:before { 182 | content: "\e6ca"; 183 | } 184 | 185 | .ic-comment-close:before { 186 | content: "\e6cb"; 187 | } 188 | 189 | .ic-bottombar-comment:before { 190 | content: "\e6cc"; 191 | } 192 | 193 | .ic-alert-info:before { 194 | content: "\e6e0"; 195 | } 196 | 197 | .ic-alert-success:before { 198 | content: "\e6e1"; 199 | } 200 | 201 | .ic-alert-error:before { 202 | content: "\e6e2"; 203 | } 204 | 205 | .ic-search-change:before { 206 | content: "\e6e3"; 207 | } 208 | 209 | .ic-more-vert:before { 210 | content: "\e6e4"; 211 | } 212 | 213 | .ic-read:before { 214 | content: "\e6e5"; 215 | } 216 | 217 | .ic-switch-order:before { 218 | content: "\e6e6"; 219 | } 220 | 221 | .ic-latest:before { 222 | content: "\e6e7"; 223 | } 224 | 225 | .ic-mode-fontsize:before { 226 | content: "\e6e8"; 227 | } 228 | 229 | .ic-alipay:before { 230 | content: "\e6e9"; 231 | } 232 | 233 | .ic-wechat-pay:before { 234 | content: "\e6ea"; 235 | } 236 | 237 | .ic-points:before { 238 | content: "\e60b"; 239 | } 240 | 241 | .ic-share-android:before { 242 | content: "\e6ee"; 243 | } 244 | 245 | .ic-share-ios:before { 246 | content: "\e6ef"; 247 | } 248 | 249 | .ic-tag:before { 250 | content: "\e6f3"; 251 | } 252 | 253 | .ic-note-show:before { 254 | content: "\e606"; 255 | } 256 | 257 | .ic-club:before { 258 | content: "\e6fb"; 259 | } 260 | 261 | .ic-audio:before { 262 | content: "\e602"; 263 | } 264 | 265 | .ic-coupon:before { 266 | content: "\e6fc"; 267 | } 268 | 269 | .ic-sign-about:before { 270 | content: "\e607"; 271 | } 272 | 273 | .ic-sign:before { 274 | content: "\e608"; 275 | } 276 | 277 | .ic-sign-notification:before { 278 | content: "\e609"; 279 | } 280 | 281 | .ic-sign-close:before { 282 | content: "\e60d"; 283 | } 284 | 285 | .ic-icon_jewel:before { 286 | content: "\e61c"; 287 | } 288 | 289 | .ic-icon_:before { 290 | content: "\e61d"; 291 | } 292 | 293 | .ic-xiadie:before { 294 | content: "\e60c"; 295 | } 296 | 297 | .ic-shangzhang:before { 298 | content: "\e60f"; 299 | } 300 | 301 | .ic-list-money:before { 302 | content: "\e63e"; 303 | } 304 | 305 | .ic-icon_comment_photo:before { 306 | content: "\e626"; 307 | } 308 | 309 | .ic-pinglunhuifu:before { 310 | content: "\e627"; 311 | } 312 | 313 | .ic-icon_comment_like:before { 314 | content: "\e628"; 315 | } 316 | 317 | .ic-like1:before { 318 | content: "\e62b"; 319 | } 320 | 321 | .ic-reward-more:before { 322 | content: "\e62d"; 323 | } 324 | 325 | .ic-liked:before { 326 | content: "\e62e"; 327 | } 328 | 329 | .ic-icon_jewel_list_bott:before { 330 | content: "\e669"; 331 | } 332 | 333 | .ic-icon_jewel_list_top:before { 334 | content: "\e66a"; 335 | } 336 | 337 | .ic-icon_jewel_lv_arrow:before { 338 | content: "\e673"; 339 | } 340 | 341 | .ic-H-liked:before { 342 | content: "\e618"; 343 | } 344 | 345 | .ic-H-like:before { 346 | content: "\e619"; 347 | } 348 | 349 | .ic-Hweixian:before { 350 | content: "\e634"; 351 | } 352 | 353 | .ic-Hlink:before { 354 | content: "\e635"; 355 | } 356 | 357 | .ic-icon_comment_like_active:before { 358 | content: "\e638"; 359 | } 360 | 361 | .ic-bangdan:before { 362 | content: "\e639"; 363 | } 364 | 365 | .ic-icon_more_arrow:before { 366 | content: "\e63a"; 367 | } 368 | 369 | .ic-zuan:before { 370 | content: "\e63b"; 371 | } 372 | 373 | .ic-share:before { 374 | content: "\e61e"; 375 | } 376 | 377 | .ic-save:before { 378 | content: "\e623"; 379 | } 380 | 381 | -------------------------------------------------------------------------------- /examples/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falstack/v-switcher/86564b05b99d204761527ce4cd109aa18bf2bb73/examples/icons.png -------------------------------------------------------------------------------- /examples/icons2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falstack/v-switcher/86564b05b99d204761527ce4cd109aa18bf2bb73/examples/icons2.png -------------------------------------------------------------------------------- /examples/item-factory.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | faker.locale = 'zh_CN' 3 | 4 | let GLOBAL_ID = 0 5 | 6 | export default new class { 7 | get(count) { 8 | let items = [], i 9 | for (i = 0; i < count; i++) { 10 | const width = 100 + ~~(Math.random() * 50) 11 | const height = 100 12 | items[i] = { 13 | id: ++GLOBAL_ID, 14 | style: { 15 | color: this.getRandomColor() 16 | }, 17 | width, 18 | height, 19 | data: Object.assign(faker.helpers.createCard(), { 20 | number_id: faker.random.number(), 21 | uuid: faker.random.uuid(), 22 | follow: false 23 | }), 24 | like: false 25 | } 26 | } 27 | return count === 1 ? items[0] : items 28 | } 29 | 30 | getRandomColor() { 31 | var colors = [ 32 | 'rgba(21,174,103,.5)', 33 | 'rgba(245,163,59,.5)', 34 | 'rgba(255,230,135,.5)', 35 | 'rgba(194,217,78,.5)', 36 | 'rgba(195,123,177,.5)', 37 | 'rgba(125,205,244,.5)' 38 | ] 39 | return colors[~~(Math.random() * colors.length)] 40 | } 41 | }() 42 | -------------------------------------------------------------------------------- /examples/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import Switcher from '../src/v-switcher' 6 | import './iconfont.css' 7 | import VueMixinStore from 'vue-mixin-store' 8 | 9 | Vue.config.productionTip = false 10 | Vue.component(Switcher.name, Switcher) 11 | Vue.component(VueMixinStore.FlowLoader.name, VueMixinStore.FlowLoader) 12 | 13 | new Vue({ 14 | el: '#app', 15 | router, 16 | store, 17 | render: h => h(App) 18 | }) 19 | -------------------------------------------------------------------------------- /examples/pages/animated.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 53 | 54 | 77 | -------------------------------------------------------------------------------- /examples/pages/carousel.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 53 | 54 | 118 | -------------------------------------------------------------------------------- /examples/pages/index.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /examples/pages/list.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 222 | 223 | 263 | -------------------------------------------------------------------------------- /examples/pages/mobile.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 198 | 199 | 283 | -------------------------------------------------------------------------------- /examples/pages/simple.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 209 | 210 | 256 | -------------------------------------------------------------------------------- /examples/pages/swipe.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 66 | 67 | 92 | -------------------------------------------------------------------------------- /examples/pages/usage.vue: -------------------------------------------------------------------------------- 1 | 368 | 369 | 442 | 443 | 552 | -------------------------------------------------------------------------------- /examples/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | Vue.use(VueRouter) 5 | 6 | export default new VueRouter({ 7 | mode: 'history', 8 | routes: [ 9 | { 10 | path: '/', 11 | name: 'index', 12 | redirect: '/homepage' 13 | }, 14 | { 15 | path: '/homepage', 16 | name: 'homepage', 17 | component: () => import('./pages/index') 18 | }, 19 | { 20 | path: '/simple', 21 | name: 'simple', 22 | component: () => import('./pages/simple') 23 | }, 24 | { 25 | path: '/animated', 26 | name: 'animated', 27 | component: () => import('./pages/animated') 28 | }, 29 | { 30 | path: '/swipe', 31 | name: 'swipe', 32 | component: () => import('./pages/swipe') 33 | }, 34 | { 35 | path: '/carousel', 36 | name: 'carousel', 37 | component: () => import('./pages/carousel') 38 | }, 39 | { 40 | path: '/usage', 41 | name: 'usage', 42 | component: () => import('./pages/usage') 43 | }, 44 | { 45 | path: '/mobile', 46 | name: 'mobile', 47 | component: () => import('./pages/mobile') 48 | }, 49 | { 50 | path: '/list', 51 | name: 'list', 52 | component: () => import('./pages/list') 53 | } 54 | ] 55 | }) 56 | -------------------------------------------------------------------------------- /examples/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import VueMixinStore from 'vue-mixin-store' 4 | import * as api from './api' 5 | 6 | const flow = VueMixinStore.FlowStore(api, true) 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | modules: { 12 | flow 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-switcher", 3 | "version": "1.1.2", 4 | "main": "dist/v-switcher.umd.min.js", 5 | "files": [ 6 | "dist" 7 | ], 8 | "keywords": [ 9 | "vue", 10 | "switcher", 11 | "carousel", 12 | "tab" 13 | ], 14 | "author": "falstack ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/falstack/v-switcher/issues" 18 | }, 19 | "homepage": "https://github.com/falstack/v-switcher", 20 | "scripts": { 21 | "dev": "vue-cli-service serve", 22 | "build": "vue-cli-service build --target lib --name v-switcher src/v-switcher.vue", 23 | "lint": "eslint --fix ./src", 24 | "docs:dev": "vuepress dev docs", 25 | "docs:build": "vuepress build docs" 26 | }, 27 | "dependencies": { 28 | "vue": "^2.6.11" 29 | }, 30 | "devDependencies": { 31 | "@vue/cli-plugin-babel": "^4.3.1", 32 | "@vue/cli-plugin-eslint": "^4.3.1", 33 | "@vue/cli-service": "^4.3.1", 34 | "@vue/eslint-config-prettier": "^6.0.0", 35 | "babel-eslint": "^10.1.0", 36 | "eslint": "^6.8.0", 37 | "eslint-plugin-prettier": "^3.1.3", 38 | "eslint-plugin-vue": "^6.2.2", 39 | "faker": "^4.1.0", 40 | "h5-vue-scroller": "^1.0.2", 41 | "less-loader": "^6.0.0", 42 | "node-sass": "^4.14.1", 43 | "sass-loader": "^8.0.2", 44 | "vue-flow-render": "^1.0.5", 45 | "vue-mixin-store": "^1.3.10", 46 | "vue-router": "^3.1.6", 47 | "vue-template-compiler": "^2.6.11", 48 | "vuepress": "^1.4.1", 49 | "vuex": "^3.3.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falstack/v-switcher/86564b05b99d204761527ce4cd109aa18bf2bb73/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-layout-tab 9 | 10 | 11 | 14 |
15 | 16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/affix.js: -------------------------------------------------------------------------------- 1 | import { on, off, getScroll, getOffset, getScrollTarget } from './utils' 2 | 3 | export default { 4 | props: { 5 | fixedTop: { // eslint-disable-line 6 | type: Number 7 | } 8 | }, 9 | data() { 10 | return { 11 | isFixed: false, 12 | showFixedShim: false, 13 | fixedShimStyle: {}, 14 | fixedHeaderStyle: {} 15 | } 16 | }, 17 | mounted() { 18 | if (this.fixedTop !== undefined) { 19 | const target = getScrollTarget(this.$el) 20 | on(target, 'scroll', this.handleScroll) 21 | on(target, 'resize', this.handleScroll) 22 | this.$nextTick(() => { 23 | this.handleScroll() 24 | }) 25 | } 26 | }, 27 | beforeDestroy() { 28 | if (this.fixedTop !== undefined) { 29 | const target = getScrollTarget(this.$el) 30 | off(target, 'scroll', this.handleScroll) 31 | off(target, 'resize', this.handleScroll) 32 | } 33 | }, 34 | methods: { 35 | handleScroll() { 36 | const isFixed = this.isFixed 37 | const scrollTop = getScroll(window, true) 38 | const elOffset = getOffset(this.$el) 39 | // Fixed Top 40 | if (elOffset.top - this.fixedTop < scrollTop && !isFixed) { 41 | this.isFixed = true 42 | this.fixedShimStyle = { 43 | width: this.$refs.headerWrap.clientWidth + 'px', 44 | height: this.$refs.headerWrap.clientHeight + 'px' 45 | } 46 | this.showFixedShim = true 47 | this.fixedHeaderStyle = { 48 | top: `${this.fixedTop}px`, 49 | left: `${elOffset.left}px`, 50 | width: `${this.$el.offsetWidth}px` 51 | } 52 | this.$emit('on-fixed', true) 53 | } else if (elOffset.top - this.fixedTop > scrollTop && isFixed) { 54 | this.showFixedShim = false 55 | this.fixedShimStyle = {} 56 | this.isFixed = false 57 | this.fixedHeaderStyle = null 58 | this.$emit('on-fixed', false) 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/slide.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | /** 3 | * sticky:默认为 true,滑动的时候会跟随手指 4 | * swipe:默认为 true,touchend 时会翻到下一页或返回上一页 5 | */ 6 | constructor(options = {}) { 7 | if (!(options.el instanceof Element)) { 8 | return 9 | } 10 | this._calcCssPrefix() 11 | this._setupConst() 12 | this._setupProps(options) 13 | this._setupSizes() 14 | this._setupIndex() 15 | this._setupStyle() 16 | this._setupTouchEvents() 17 | return this 18 | } 19 | 20 | prev(custom = true) { 21 | if (this.moving || (this.activeIndex === 0 && !this.continuous)) { 22 | return 23 | } 24 | if (this.activeIndex === 0 && this.continuous) { 25 | this._continuousAnimation(false) 26 | return 27 | } 28 | if (custom) { 29 | this.activeIndex-- 30 | } else { 31 | if (this._isValidSlide()) { 32 | this._calcActiveIndex(false) 33 | this._animation() 34 | return 35 | } 36 | this._animation(false) 37 | } 38 | } 39 | 40 | next(custom = true) { 41 | if ( 42 | this.moving || 43 | (this.activeIndex === this.slideCount - 1 && !this.continuous) 44 | ) { 45 | return 46 | } 47 | if (this.activeIndex === this.slideCount - 1 && this.continuous) { 48 | this._continuousAnimation(true) 49 | return 50 | } 51 | if (custom) { 52 | this.activeIndex++ 53 | } else { 54 | if (this._isValidSlide()) { 55 | this._calcActiveIndex(true) 56 | this._animation() 57 | return 58 | } 59 | this._animation(false) 60 | } 61 | } 62 | 63 | slide(index) { 64 | if ( 65 | this.moving || 66 | index === this.activeIndex || 67 | index < 0 || 68 | index > this.slideCount - 1 69 | ) { 70 | return 71 | } 72 | this.activeIndex = index 73 | this._animation() 74 | } 75 | 76 | scroll(left) { 77 | this.moving = true 78 | const { cssPrefix, duration, style } = this 79 | requestAnimationFrame(() => { 80 | style[`${cssPrefix}transition-duration`] = `${duration}ms` 81 | style[`${cssPrefix}transform`] = `translateX(${left}px)` 82 | setTimeout(() => { 83 | style[`${cssPrefix}transition-duration`] = '' 84 | this.currentLeft = left 85 | this.lastLeft = left 86 | this.moving = false 87 | }, duration) 88 | }) 89 | } 90 | 91 | update(options = {}) { 92 | if (!this.cssPrefix) { 93 | return 94 | } 95 | if (this.adjust) { 96 | this.slideCount = this.el.children.length 97 | } 98 | if (options) { 99 | if (options.index !== undefined) { 100 | this.activeIndex = options.index 101 | } 102 | if (options.maxLeft !== undefined) { 103 | this.maxLeft = Math.abs(options.maxLeft) 104 | } 105 | } 106 | this.destroy() 107 | this._setupSizes() 108 | this._setupIndex() 109 | this._setupTouchEvents() 110 | } 111 | 112 | play(timeout = 1000) { 113 | const time = Math.max(timeout, this.duration) 114 | if (this.autoplayTimer) { 115 | return 116 | } 117 | this.continuous = true 118 | this.sticky = false 119 | this.autoplayTimer = setInterval(() => { 120 | this.next() 121 | }, time) 122 | } 123 | 124 | stop() { 125 | if (!this.autoplayTimer) { 126 | return 127 | } 128 | clearInterval(this.autoplayTimer) 129 | this.autoplayTimer = 0 130 | } 131 | 132 | destroy() { 133 | const { el, events } = this 134 | if (!el) { 135 | return 136 | } 137 | el.removeEventListener('touchstart', events.touchstart, { 138 | capture: true, 139 | passive: true 140 | }) 141 | el.removeEventListener('touchmove', events.touchmove, true) 142 | el.removeEventListener('touchend', events.touchend, { 143 | capture: true, 144 | passive: true 145 | }) 146 | this.stop() 147 | } 148 | 149 | _setupTouchEvents() { 150 | const { el, events } = this 151 | if (!el) { 152 | return 153 | } 154 | events.touchstart = this._start.bind(this) 155 | events.touchmove = this._move.bind(this) 156 | events.touchend = this._end.bind(this) 157 | el.addEventListener('touchstart', events.touchstart, { 158 | capture: true, 159 | passive: true 160 | }) 161 | el.addEventListener('touchmove', events.touchmove, true) 162 | el.addEventListener('touchend', events.touchend, { 163 | capture: true, 164 | passive: true 165 | }) 166 | } 167 | 168 | _setupConst() { 169 | this.startPoint = { 170 | x: 0, 171 | y: 0 172 | } 173 | this.deltaPoint = { 174 | x: 0, 175 | y: 0 176 | } 177 | this.maxDeltaPoint = { 178 | x: 0, 179 | y: 0 180 | } 181 | this.lastLeft = 0 182 | this.currentLeft = 0 183 | this.moving = false 184 | this.startAt = 0 185 | this.events = {} 186 | this.autoplayTimer = 0 187 | } 188 | 189 | _setupProps(options) { 190 | const duration = 191 | options.duration === undefined ? 300 : Math.abs(options.duration) 192 | this.el = options.el 193 | this.style = options.el.style 194 | this.duration = duration 195 | this.sticky = duration 196 | ? options.sticky === undefined 197 | ? true 198 | : options.sticky 199 | : false 200 | this.swipe = options.swipe === undefined ? true : options.swipe 201 | this.disabled = options.disabled || false 202 | this.continuous = options.continuous || false 203 | this.adjust = options.adjust === undefined ? true : options.adjust 204 | this.maxLeft = options.maxLeft ? Math.abs(options.maxLeft) : 0 205 | this.autoCalcMaxLeft = !options.maxLeft 206 | this.callback = options.callback 207 | this.beforeCallback = options.beforeCallback 208 | this.slideCount = Math.max(options.el.children.length, 1) 209 | this.activeIndex = options.index 210 | ? Math.max(Math.min(options.index, this.slideCount - 1), 0) 211 | : 0 212 | } 213 | 214 | _setupStyle() { 215 | if (!this.adjust) { 216 | return 217 | } 218 | const { style, slideCount, slideWidth } = this 219 | style.width = `${slideCount * 100}%` 220 | if (slideCount > 1) { 221 | const slides = this.el.children 222 | ;[].forEach.call(slides, item => { 223 | const { style } = item 224 | style.width = `${slideWidth}px` 225 | style.float = 'left' 226 | }) 227 | } 228 | } 229 | 230 | _setupIndex() { 231 | if (!this.activeIndex) { 232 | return 233 | } 234 | const left = (-this.activeIndex * this.maxLeft) / (this.slideCount - 1) 235 | this.lastLeft = left 236 | this.currentLeft = left 237 | this._translate(left) 238 | } 239 | 240 | _setupSizes() { 241 | if (!this.el) { 242 | return 243 | } 244 | const offsetWidth = this.el.parentNode.offsetWidth 245 | this.slideWidth = offsetWidth 246 | if (this.autoCalcMaxLeft) { 247 | this.maxLeft = offsetWidth * this.slideCount - offsetWidth 248 | } 249 | } 250 | 251 | _start(event) { 252 | if (this.moving || this.disabled) { 253 | return 254 | } 255 | const point = event.touches[0] 256 | this.startPoint = { 257 | x: point.pageX, 258 | y: point.pageY 259 | } 260 | this.startAt = +new Date() 261 | this._lockedSwipeEvents() 262 | } 263 | 264 | _move(event) { 265 | if (this.moving || this.disabled) { 266 | return 267 | } 268 | const point = event.touches[0] 269 | const start = this.startPoint 270 | const max = this.maxDeltaPoint 271 | const delta = { 272 | x: point.pageX - start.x, 273 | y: point.pageY - start.y 274 | } 275 | this.maxDeltaPoint = { 276 | x: Math.max(max.x, Math.abs(delta.x)), 277 | y: Math.max(max.y, Math.abs(delta.y)) 278 | } 279 | if (this._isVerticalScroll(this.maxDeltaPoint)) { 280 | return 281 | } 282 | this.deltaPoint = delta 283 | const lastLeft = this.lastLeft 284 | let resultX = delta.x + lastLeft 285 | if (resultX > 0) { 286 | resultX = 0 287 | } else if (resultX + this.maxLeft < 0) { 288 | resultX = -this.maxLeft 289 | } 290 | if (resultX === this.currentLeft) { 291 | return 292 | } 293 | if (this.sticky) { 294 | this._translate(resultX) 295 | this.currentLeft = resultX 296 | } 297 | } 298 | 299 | _end() { 300 | if (this.moving || this.disabled) { 301 | return 302 | } 303 | this.lastLeft = this.currentLeft 304 | this.maxDeltaPoint = { 305 | x: 0, 306 | y: 0 307 | } 308 | this._unlockSwipeEvents() 309 | const delta = this.deltaPoint 310 | if (!this.sticky && this._isVerticalScroll(delta)) { 311 | return 312 | } 313 | if (this.swipe) { 314 | delta.x > 0 ? this.prev(false) : this.next(false) 315 | } else { 316 | this._calcActiveIndex(delta.x < 0) 317 | } 318 | } 319 | 320 | _continuousAnimation(isNext) { 321 | this.moving = true 322 | const { 323 | cssPrefix, 324 | slideCount, 325 | duration, 326 | slideWidth, 327 | style, 328 | el, 329 | beforeCallback 330 | } = this 331 | const slides = el.children 332 | const slide = isNext ? slides[0] : slides[slideCount - 1] 333 | const left = isNext ? -slideCount * slideWidth : slideWidth 334 | const activeIndex = isNext ? 0 : slideCount - 1 335 | slide.style[`${cssPrefix}transform`] = `translateX(${(isNext ? 100 : -100) * 336 | slideCount}%)` 337 | beforeCallback && beforeCallback(activeIndex) 338 | requestAnimationFrame(() => { 339 | style[`${cssPrefix}transition-duration`] = `${duration}ms` 340 | style[`${cssPrefix}transform`] = `translateX(${left}px)` 341 | setTimeout(() => { 342 | const finalLeft = isNext ? 0 : -activeIndex * slideWidth 343 | style[`${cssPrefix}transition-duration`] = '' 344 | style[`${cssPrefix}transform`] = `translateX(${finalLeft}px)` 345 | slide.style[`${cssPrefix}transform`] = '' 346 | this.currentLeft = finalLeft 347 | this.lastLeft = finalLeft 348 | this.activeIndex = activeIndex 349 | this.moving = false 350 | this.callback && this.callback(activeIndex) 351 | }, duration) 352 | }) 353 | } 354 | 355 | _animation(emit = true) { 356 | this.moving = true 357 | const { cssPrefix, duration, activeIndex, style, beforeCallback } = this 358 | const left = -activeIndex * this.slideWidth 359 | beforeCallback && beforeCallback(activeIndex) 360 | requestAnimationFrame(() => { 361 | style[`${cssPrefix}transition-duration`] = `${duration}ms` 362 | style[`${cssPrefix}transform`] = `translateX(${left}px)` 363 | setTimeout(() => { 364 | style[`${cssPrefix}transition-duration`] = '' 365 | this.currentLeft = left 366 | this.lastLeft = left 367 | this.moving = false 368 | if (emit) { 369 | this.callback && this.callback(activeIndex) 370 | } 371 | }, duration) 372 | }) 373 | } 374 | 375 | _translate(left) { 376 | requestAnimationFrame(() => { 377 | this.style[`${this.cssPrefix}transform`] = `translateX(${left}px)` 378 | }) 379 | } 380 | 381 | _lockedSwipeEvents() { 382 | this.style['touch-action'] = 'none' 383 | this.style.willChange = 'transform' 384 | } 385 | 386 | _unlockSwipeEvents() { 387 | this.style['touch-action'] = 'auto' 388 | this.style.willChange = '' 389 | } 390 | 391 | _isVerticalScroll(delta) { 392 | return Math.abs(delta.x) < Math.abs(delta.y) * 3 393 | } 394 | 395 | _calcCssPrefix() { 396 | let result = '' 397 | const regex = /^(Webkit|Khtml|Moz|ms|O)(?=[A-Z])/ 398 | const styleDeclaration = document.getElementsByTagName('script')[0].style 399 | // eslint-disable-next-line no-unused-vars 400 | for (const prop in styleDeclaration) { 401 | if (regex.test(prop)) { 402 | result = '-' + prop.match(regex)[0].toLowerCase() + '-' 403 | } 404 | } 405 | 406 | if (!result && 'WebkitOpacity' in styleDeclaration) { 407 | result = '-webkit-' 408 | } 409 | if (!result && 'KhtmlOpacity' in styleDeclaration) { 410 | result = '-khtml-' 411 | } 412 | this.cssPrefix = result 413 | } 414 | 415 | _calcActiveIndex(isNext) { 416 | if (this.sticky) { 417 | this.activeIndex = isNext 418 | ? Math.ceil(Math.abs(this.currentLeft) / this.slideWidth) 419 | : Math.floor(Math.abs(this.currentLeft) / this.slideWidth) 420 | } else { 421 | isNext ? this.activeIndex++ : this.activeIndex-- 422 | } 423 | } 424 | 425 | _isValidSlide() { 426 | const x = Math.abs(this.deltaPoint.x) 427 | return ( 428 | (Number(+new Date() - this.startAt) < 250 && x > 20) || 429 | x > this.slideWidth / 2 430 | ) 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /src/swipe.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Swipe 2.0 3 | * 4 | * Brad Birdsall 5 | * Copyright 2013, MIT License 6 | * 7 | */ 8 | export default function Swipe(container, options) { 9 | 'use strict' 10 | 11 | // utilities 12 | var noop = function() {} // simple no operation function 13 | var offloadFn = function(fn) { 14 | setTimeout(fn || noop, 0) 15 | } // offload a functions execution 16 | 17 | var isAndroid = /android/.test(window.navigator.userAgent.toLocaleLowerCase()) 18 | var lastMoveAt = 0 19 | 20 | // quit if no root element 21 | if (!container) return 22 | var element = container.children[0] 23 | var slides, slidePos, width 24 | options = options || {} 25 | var index = parseInt(options.startSlide, 10) || 0 26 | var speed = options.speed || 300 27 | var disabled = options.disabled || false 28 | options.continuous = 29 | options.continuous !== undefined ? options.continuous : true 30 | 31 | function setup() { 32 | // cache slides 33 | slides = element.children 34 | 35 | // set continuous to false if only one slide 36 | if (slides.length < 2) options.continuous = false 37 | 38 | //special case if two slides 39 | if (options.continuous && slides.length < 3) { 40 | element.appendChild(slides[0].cloneNode(true)) 41 | element.appendChild(element.children[1].cloneNode(true)) 42 | slides = element.children 43 | } 44 | 45 | // create an array to store current positions of each slide 46 | slidePos = new Array(slides.length) 47 | 48 | // determine width of each slide 49 | width = slides[0].getBoundingClientRect().width || slides[0].offsetWidth 50 | 51 | element.style.width = slides.length * width + 'px' 52 | element.parentElement.style.width = width + 'px' 53 | 54 | // stack elements 55 | var pos = slides.length 56 | while (pos--) { 57 | var slide = slides[pos] 58 | 59 | slide.style.width = width + 'px' 60 | slide.setAttribute('data-index', pos) 61 | 62 | slide.style.left = pos * -width + 'px' 63 | slide.style.zIndex = slides.length - pos 64 | move(pos, index > pos ? -width : index < pos ? width : 0, 0) 65 | } 66 | 67 | // reposition elements before and after index 68 | if (options.continuous) { 69 | move(circle(index - 1), -width, 0) 70 | move(circle(index + 1), width, 0) 71 | } 72 | 73 | container.style.visibility = 'visible' 74 | } 75 | 76 | function prev() { 77 | if (options.continuous) slide(index - 1) 78 | else if (index) slide(index - 1) 79 | } 80 | 81 | function next() { 82 | if (options.continuous) slide(index + 1) 83 | else if (index < slides.length - 1) slide(index + 1) 84 | } 85 | 86 | function circle(index) { 87 | // a simple positive modulo using slides.length 88 | return (slides.length + (index % slides.length)) % slides.length 89 | } 90 | 91 | function slide(to, slideSpeed) { 92 | // do nothing if already on requested slide 93 | if (index === to) return 94 | 95 | var direction = Math.abs(index - to) / (index - to) // 1: backward, -1: forward 96 | 97 | // get the actual position of the slide 98 | if (options.continuous) { 99 | var natural_direction = direction 100 | direction = -slidePos[circle(to)] / width 101 | 102 | // if going forward but to < index, use to = slides.length + to 103 | // if going backward but to > index, use to = -slides.length + to 104 | if (direction !== natural_direction) to = -direction * slides.length + to 105 | } 106 | 107 | var diff = Math.abs(index - to) - 1 108 | 109 | // move all the slides between index and to in the right direction 110 | while (diff--) 111 | move(circle((to > index ? to : index) - diff - 1), width * direction, 0) 112 | 113 | to = circle(to) 114 | 115 | move(index, width * direction, slideSpeed || speed) 116 | move(to, 0, slideSpeed || speed) 117 | 118 | if (options.continuous) 119 | move(circle(to - direction), -(width * direction), 0) // we need to get the next in place 120 | 121 | index = to 122 | offloadFn(options.callback && options.callback(index, slides[index])) 123 | lastMoveAt = Date.now() 124 | } 125 | 126 | function move(index, dist, speed) { 127 | translate(index, dist, speed) 128 | slidePos[index] = dist 129 | } 130 | 131 | function translate(index, dist, speed) { 132 | var slide = slides[index] 133 | var style = slide && slide.style 134 | 135 | if (!style) return 136 | 137 | style.webkitTransitionDuration = style.MozTransitionDuration = style.msTransitionDuration = style.OTransitionDuration = style.transitionDuration = 138 | speed + 'ms' 139 | 140 | style.webkitTransform = style.msTransform = style.MozTransform = style.OTransform = 141 | 'translateX(' + dist + 'px)' 142 | } 143 | 144 | // setup auto slideshow 145 | var delay = options.auto || 0 146 | var interval 147 | 148 | function begin() { 149 | interval = setTimeout(next, delay) 150 | } 151 | 152 | function stop() { 153 | delay = 0 154 | interval && clearTimeout(interval) 155 | } 156 | 157 | // setup initial vars 158 | var start = {} 159 | var delta = {} 160 | var isScrolling 161 | 162 | // setup event capturing 163 | var events = { 164 | handleEvent: function(event) { 165 | switch (event.type) { 166 | case 'touchstart': 167 | this.start(event) 168 | break 169 | case 'touchmove': 170 | this.move(event) 171 | break 172 | case 'touchend': 173 | offloadFn(this.end(event)) 174 | break 175 | case 'webkitTransitionEnd': 176 | case 'msTransitionEnd': 177 | case 'oTransitionEnd': 178 | case 'otransitionend': 179 | case 'transitionend': 180 | offloadFn(this.transitionEnd(event)) 181 | break 182 | case 'resize': 183 | offloadFn(setup) 184 | break 185 | } 186 | 187 | if (options.stopPropagation) event.stopPropagation() 188 | }, 189 | start: function(event) { 190 | if (disabled) { 191 | return 192 | } 193 | 194 | var touches = event.touches[0] 195 | 196 | // measure start values 197 | start = { 198 | // get initial touch coords 199 | x: touches.pageX, 200 | y: touches.pageY, 201 | 202 | // store time to determine touch duration 203 | time: +new Date() 204 | } 205 | 206 | // used for testing first move event 207 | isScrolling = undefined 208 | 209 | // reset delta and end measurements 210 | delta = {} 211 | 212 | // attach touchmove and touchend listeners 213 | element.addEventListener('touchmove', this, false) 214 | element.addEventListener('touchend', this, false) 215 | }, 216 | move: function(event) { 217 | if (disabled) { 218 | return 219 | } 220 | 221 | // ensure swiping with one touch and not pinching 222 | if (event.touches.length > 1 || (event.scale && event.scale !== 1)) return 223 | 224 | if (options.disableScroll) event.preventDefault() 225 | 226 | var touches = event.touches[0] 227 | 228 | // measure change in x and y 229 | delta = { 230 | x: touches.pageX - start.x, 231 | y: touches.pageY - start.y 232 | } 233 | 234 | // determine if scrolling test has run - one time test 235 | if (typeof isScrolling === 'undefined') { 236 | isScrolling = !!(isScrolling || Math.abs(delta.x) < Math.abs(delta.y)) 237 | } 238 | 239 | // if user is not trying to scroll vertically 240 | if (!isScrolling) { 241 | // prevent native scrolling 242 | event.preventDefault() 243 | if (isAndroid) { 244 | if (Math.abs(delta.x) < Math.abs(delta.y) * 3) { 245 | stop() 246 | return 247 | } 248 | } else { 249 | event.stopPropagation() 250 | } 251 | 252 | // stop slideshow 253 | stop() 254 | 255 | // increase resistance if first or last slide 256 | if (options.continuous) { 257 | // we don't add resistance at the end 258 | 259 | translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0) 260 | translate(index, delta.x + slidePos[index], 0) 261 | translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0) 262 | } else { 263 | delta.x = 264 | delta.x / 265 | ((!index && delta.x > 0) || // if first slide and sliding left 266 | (index === slides.length - 1 && // or if last slide and sliding right 267 | delta.x < 0) // and if sliding at all 268 | ? Math.abs(delta.x) / width + 1 // determine resistance level 269 | : 1) // no resistance if false 270 | 271 | if (index === 0 && delta.x > 0) { 272 | return 273 | } 274 | if (index === element.children.length - 1 && delta.x < 0) { 275 | return 276 | } 277 | // translate 1:1 278 | translate(index - 1, delta.x + slidePos[index - 1], 0) 279 | translate(index, delta.x + slidePos[index], 0) 280 | translate(index + 1, delta.x + slidePos[index + 1], 0) 281 | } 282 | } 283 | }, 284 | end: function() { 285 | // measure duration 286 | var duration = +new Date() - start.time 287 | 288 | // determine if slide attempt triggers next/prev slide 289 | var isValidSlide = 290 | (Number(duration) < 250 && // if slide duration is less than 250ms 291 | Math.abs(delta.x) > 20) || // and if slide amt is greater than 20px 292 | Math.abs(delta.x) > width / 2 // or if slide amt is greater than half the width 293 | 294 | // determine if slide attempt is past start and end 295 | var isPastBounds = 296 | (!index && delta.x > 0) || // if first slide and slide amt is greater than 0 297 | (index === slides.length - 1 && delta.x < 0) // or if last slide and slide amt is less than 0 298 | 299 | if (options.continuous) isPastBounds = false 300 | 301 | // determine direction of swipe (true:right, false:left) 302 | var direction = delta.x < 0 303 | 304 | // if not scrolling vertically 305 | if (!isScrolling) { 306 | if (isValidSlide && !isPastBounds) { 307 | if (direction) { 308 | if (options.continuous) { 309 | // we need to get the next in this direction in place 310 | 311 | move(circle(index - 1), -width, 0) 312 | move(circle(index + 2), width, 0) 313 | } else { 314 | move(index - 1, -width, 0) 315 | } 316 | 317 | move(index, slidePos[index] - width, speed) 318 | move(circle(index + 1), slidePos[circle(index + 1)] - width, speed) 319 | index = circle(index + 1) 320 | } else { 321 | if (options.continuous) { 322 | // we need to get the next in this direction in place 323 | 324 | move(circle(index + 1), width, 0) 325 | move(circle(index - 2), -width, 0) 326 | } else { 327 | move(index + 1, width, 0) 328 | } 329 | 330 | move(index, slidePos[index] + width, speed) 331 | move(circle(index - 1), slidePos[circle(index - 1)] + width, speed) 332 | index = circle(index - 1) 333 | } 334 | lastMoveAt = Date.now() 335 | options.callback && options.callback(index, slides[index]) 336 | } else { 337 | if (options.continuous) { 338 | move(circle(index - 1), -width, speed) 339 | move(index, 0, speed) 340 | move(circle(index + 1), width, speed) 341 | } else { 342 | move(index - 1, -width, speed) 343 | move(index, 0, speed) 344 | move(index + 1, width, speed) 345 | } 346 | } 347 | } 348 | 349 | // kill touchmove and touchend event listeners until touchstart called again 350 | element.removeEventListener('touchmove', events, false) 351 | element.removeEventListener('touchend', events, false) 352 | }, 353 | transitionEnd: function(event) { 354 | if (parseInt(event.target.getAttribute('data-index'), 10) === index) { 355 | if (delay) begin() 356 | 357 | options.transitionEnd && 358 | options.transitionEnd.call(event, index, slides[index]) 359 | } 360 | } 361 | } 362 | 363 | // trigger setup 364 | setup() 365 | 366 | // start auto slideshow if applicable 367 | if (delay) begin() 368 | 369 | // add event listeners 370 | element.addEventListener('touchstart', events, false) 371 | element.addEventListener('webkitTransitionEnd', events, false) 372 | element.addEventListener('msTransitionEnd', events, false) 373 | element.addEventListener('oTransitionEnd', events, false) 374 | element.addEventListener('otransitionend', events, false) 375 | element.addEventListener('transitionend', events, false) 376 | window.addEventListener('resize', events, false) 377 | 378 | // expose the Swipe API 379 | return { 380 | slide: function(to, speed) { 381 | if (speed && Date.now() - lastMoveAt <= speed) { 382 | return 383 | } 384 | // cancel slideshow 385 | stop() 386 | 387 | slide(to, speed) 388 | }, 389 | prev: function() { 390 | // cancel slideshow 391 | stop() 392 | 393 | prev() 394 | }, 395 | next: function() { 396 | // cancel slideshow 397 | stop() 398 | 399 | next() 400 | } 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export const getMatchedRouteIndex = (headers, path) => { 2 | let result = -1 3 | headers 4 | .map(_ => _.route) 5 | .forEach((route, index) => { 6 | if (path.startsWith(route)) { 7 | result = index 8 | } 9 | }) 10 | return result 11 | } 12 | 13 | export const on = (function() { 14 | if (typeof window === 'undefined') { 15 | return 16 | } 17 | if (document.addEventListener) { 18 | return function(element, event, handler) { 19 | if (element && event && handler) { 20 | element.addEventListener(event, handler, false) 21 | } 22 | } 23 | } else { 24 | return function(element, event, handler) { 25 | if (element && event && handler) { 26 | element.attachEvent('on' + event, handler) 27 | } 28 | } 29 | } 30 | })() 31 | 32 | export const off = (function() { 33 | if (typeof window === 'undefined') { 34 | return 35 | } 36 | if (document.removeEventListener) { 37 | return function(element, event, handler) { 38 | if (element && event) { 39 | element.removeEventListener(event, handler, false) 40 | } 41 | } 42 | } else { 43 | return function(element, event, handler) { 44 | if (element && event) { 45 | element.detachEvent('on' + event, handler) 46 | } 47 | } 48 | } 49 | })() 50 | 51 | export const getScroll = (target, top) => { 52 | const prop = top ? 'pageYOffset' : 'pageXOffset' 53 | const method = top ? 'scrollTop' : 'scrollLeft' 54 | let ret = target[prop] 55 | if (typeof ret !== 'number') { 56 | ret = window.document.documentElement[method] 57 | } 58 | return ret 59 | } 60 | 61 | export const getOffset = element => { 62 | const rect = element.getBoundingClientRect() 63 | const scrollTop = getScroll(window, true) 64 | const scrollLeft = getScroll(window) 65 | const docEl = window.document.body 66 | const clientTop = docEl.clientTop || 0 67 | const clientLeft = docEl.clientLeft || 0 68 | return { 69 | top: rect.top + scrollTop - clientTop, 70 | left: rect.left + scrollLeft - clientLeft 71 | } 72 | } 73 | 74 | export const getScrollTarget = dom => { 75 | let el = dom 76 | if (!el) { 77 | return null 78 | } 79 | while ( 80 | el && 81 | el.tagName !== 'HTML' && 82 | el.tagName !== 'BOYD' && 83 | el.nodeType === 1 84 | ) { 85 | const overflowY = window.getComputedStyle(el).overflowY 86 | if (overflowY === 'scroll' || overflowY === 'auto') { 87 | if (el.tagName === 'HTML' || el.tagName === 'BODY') { 88 | return document 89 | } 90 | return el 91 | } 92 | el = el.parentNode 93 | } 94 | return document 95 | } 96 | -------------------------------------------------------------------------------- /src/v-switcher.vue: -------------------------------------------------------------------------------- 1 | 172 | 173 | 259 | 260 | 975 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const npmCfg = require('./package.json') 3 | const CopyWebpackPlugin = require('copy-webpack-plugin') 4 | 5 | const banner = [ 6 | npmCfg.name + ' v' + npmCfg.version, 7 | '(c) ' + new Date().getFullYear() + ' ' + npmCfg.author, 8 | npmCfg.homepage 9 | ].join('\n') 10 | 11 | module.exports = { 12 | productionSourceMap: false, 13 | pages: { 14 | index: { 15 | entry: 'examples/main.js', 16 | template: 'public/index.html', 17 | filename: 'index.html' 18 | } 19 | }, 20 | configureWebpack: { 21 | plugins: [ 22 | new webpack.BannerPlugin(banner), 23 | new CopyWebpackPlugin([ 24 | { from: './src/v-switcher.vue' }, 25 | { from: './src/swipe.js' }, 26 | { from: './src/affix.js' }, 27 | { from: './src/utils.js' } 28 | ]) 29 | ] 30 | }, 31 | css: { 32 | extract: true 33 | } 34 | } 35 | --------------------------------------------------------------------------------