├── static └── .gitkeep ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── src ├── assets │ └── logo.png ├── components │ ├── next1.vue │ └── HelloWorld.vue ├── main.js ├── router │ └── index.js └── App.vue ├── .editorconfig ├── .gitignore ├── .babelrc ├── .postcssrc.js ├── index.html ├── package.json └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mosasa/vue-transition-practice/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/next1.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | 7 | Vue.config.productionTip = false 8 | 9 | /* eslint-disable no-new */ 10 | new Vue({ 11 | el: '#app', 12 | router, 13 | components: { App }, 14 | template: '' 15 | }) 16 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import HelloWorld from '@/components/HelloWorld' 4 | import Next1 from '@/components/next1' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'HelloWorld', 13 | component: HelloWorld 14 | }, 15 | { 16 | path: '/next1', 17 | name: 'Next1', 18 | component: Next1 19 | } 20 | ], 21 | // scrollBehavior (to, from, savedPosition) { 22 | // return { x: 0, y: 0 } 23 | // } 24 | }) 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "wms <1462546799@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "vue": "^2.5.2", 14 | "vue-router": "^3.0.1" 15 | }, 16 | "devDependencies": { 17 | "autoprefixer": "^7.1.2", 18 | "babel-core": "^6.22.1", 19 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 20 | "babel-loader": "^7.1.1", 21 | "babel-plugin-syntax-jsx": "^6.18.0", 22 | "babel-plugin-transform-runtime": "^6.22.0", 23 | "babel-plugin-transform-vue-jsx": "^3.5.0", 24 | "babel-preset-env": "^1.3.2", 25 | "babel-preset-stage-2": "^6.22.0", 26 | "chalk": "^2.0.1", 27 | "copy-webpack-plugin": "^4.0.1", 28 | "css-loader": "^0.28.0", 29 | "extract-text-webpack-plugin": "^3.0.0", 30 | "file-loader": "^1.1.4", 31 | "friendly-errors-webpack-plugin": "^1.6.1", 32 | "html-webpack-plugin": "^2.30.1", 33 | "node-notifier": "^5.1.2", 34 | "optimize-css-assets-webpack-plugin": "^3.2.0", 35 | "ora": "^1.2.0", 36 | "portfinder": "^1.0.13", 37 | "postcss-import": "^11.0.0", 38 | "postcss-loader": "^2.0.8", 39 | "postcss-url": "^7.2.1", 40 | "rimraf": "^2.6.0", 41 | "semver": "^5.3.0", 42 | "shelljs": "^0.7.6", 43 | "uglifyjs-webpack-plugin": "^1.1.1", 44 | "url-loader": "^0.5.8", 45 | "vue-loader": "^13.3.0", 46 | "vue-style-loader": "^3.0.1", 47 | "vue-template-compiler": "^2.5.2", 48 | "webpack": "^3.6.0", 49 | "webpack-bundle-analyzer": "^2.9.0", 50 | "webpack-dev-server": "^2.9.1", 51 | "webpack-merge": "^4.1.0" 52 | }, 53 | "engines": { 54 | "node": ">= 6.0.0", 55 | "npm": ">= 3.0.0" 56 | }, 57 | "browserslist": [ 58 | "> 1%", 59 | "last 2 versions", 60 | "not ie <= 8" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | 24 | /** 25 | * Source Maps 26 | */ 27 | 28 | // https://webpack.js.org/configuration/devtool/#development 29 | devtool: 'cheap-module-eval-source-map', 30 | 31 | // If you have problems debugging vue-files in devtools, 32 | // set this to false - it *may* help 33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 34 | cacheBusting: true, 35 | 36 | cssSourceMap: true 37 | }, 38 | 39 | build: { 40 | // Template for index.html 41 | index: path.resolve(__dirname, '../dist/index.html'), 42 | 43 | // Paths 44 | assetsRoot: path.resolve(__dirname, '../dist'), 45 | assetsSubDirectory: 'static', 46 | assetsPublicPath: '/', 47 | 48 | /** 49 | * Source Maps 50 | */ 51 | 52 | productionSourceMap: true, 53 | // https://webpack.js.org/configuration/devtool/#production 54 | devtool: '#source-map', 55 | 56 | // Gzip off by default as many popular static hosts such as 57 | // Surge or Netlify already gzip all static assets for you. 58 | // Before setting to `true`, make sure to: 59 | // npm install --save-dev compression-webpack-plugin 60 | productionGzip: false, 61 | productionGzipExtensions: ['js', 'css'], 62 | 63 | // Run the build command with an extra argument to 64 | // View the bundle analyzer report after build finishes: 65 | // `npm run build --report` 66 | // Set to `true` or `false` to always turn it on or off 67 | bundleAnalyzerReport: process.env.npm_config_report 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 写在前面的思考 2 | * 如何匹配到相应需要跳转的页面? 3 | * 如何判断是“前进”还是“后退”而后使用不同的动画方式? 4 | * 如何对不同的跳转设置动画效果? 5 | ## 实现过程 6 | ### 一、vue路由匹配 7 | 创建vue实例,匹配路由。 8 | 用Vue.js + Vue Router创建单页应用,是非常简单的。使用Vue.js,我们可以通过组合组件来组成应用程序,将Vue Router 添加进来之后,我们需要做的是,将组件(components)映射到路由(routes),然后告诉Vue Router 在哪里渲染它们。 9 | 10 | ``` 11 | import Vue from 'vue' 12 | import Router from 'vue-router' 13 | Vue.use(Router) 14 | //如果使用模块化机制编程,导入Vue和VueRouter,就需要调用Vue.use(Router) 15 | ``` 16 | 接下来就可以进行路由组件的映射: 17 | (路由)组件的定义可以自行定义,当然,为了践行模块化组件化思想,多是从其他文件import进来。以下以简单的“登录->主页->点单->结算”四个页面的交互为例: 18 | 19 | ``` 20 | import Login from '@/components/login' 21 | import Index from '@/components/index' 22 | import PointList from '@/components/pointList/pointList' 23 | import SettLement from '@/components/pointList/settlement' 24 | 25 | //创建router实例,然后传入‘routes’配置 26 | export default new Router({ 27 | //routes配置可以直接传入,也可以先定义后使用 28 | //每个路由都应该映射一个组件,其中component可以是通过Vue.extend()创建的组件构造器,或者只是一个组件配饰对象。(今天暂时不考虑嵌套路由的情况) 29 | routes: [ 30 | { 31 | path: '/', // 登录 32 | name: 'Login', 33 | component: Login 34 | }, 35 | { 36 | path: '/index', // 主页 37 | name: 'Index', 38 | component: Index 39 | }, 40 | { 41 | path: '/pointList', // 点单 42 | name: 'PointList', 43 | component: PointList 44 | }, 45 | { 46 | path: '/settLement', // 结算 47 | name: 'SettLement', 48 | component: SettLement 49 | } 50 | ] 51 | }) 52 | ``` 53 | ### 二、路由跳转 $router 54 | 55 | ``` 56 | 组件路由除了使用全局组件 router-link 来实现点击跳转(相当于按钮)外,还可以使用组件本身具有的一个实例对象$router及其一些属性来达到目标。 57 | $router 是VueRouter的一个实例对象,相当于一个全局的路由器对象。在Vue实例内部,你可以通过$router访问路由实例,里面含有很多属性和子对象 58 | ,例如history对象,经常用到的跳转链接就可以调用this.$router.push,this.$router.push会往history栈中添加一个新记录。 59 | ``` 60 | 声明式|编程式 61 | ---|:--: 62 | 等同于调用 router.push(...) 64 | (...)该方法的参数可以是一个字符串,或者一个描述地址的对象: 65 | 66 | ``` 67 | // 字符串 68 | router.push('home') 69 | // 对象 70 | router.push({ path: 'home' }) 71 | // 命名的路由 72 | router.push({ name: 'user', params: { userId: 123 }}) 73 | // 带查询参数,变成 /register?plan=private 74 | router.push({ path: 'register', query: { plan: 'private' }}) 75 | ``` 76 | 组件路由跳转实例: 77 | 1. 78 | ``` 79 | 80 | 81 | 点单 82 | 83 | ``` 84 | 2. 85 | 86 | ``` 87 |
v 1.0
88 | //Js: 89 | methods: { 90 | goPage(url, param) { 91 | this.$router.push({ name: url }); 92 | } 93 | } 94 | ``` 95 | ### 三、vue路由对象$route(只读) 96 | 在使用了vue-router的应用中,路由对象会被注入每个组件中,赋值为this.$route,并且当路由切换时,路由对象会被更新。所以route相当于当前正在跳转的路由对象,可以从里面获取name,path,params,query等,即包含了当前URL解析得到的信息,还有URL匹配到的路由记录。 97 | 路由对象暴露了以下属性(常见): 98 | 1. $route.path 99 | 100 | ``` 101 | 字符串(string)。等于当前路由对象的路径,会被解析为绝对路径,如:http://example.com/#/login?name=aa,this.$route.path 102 | ,输出“/login”,即对应上面1中路由匹配时routes配置中的“path”; 103 | ``` 104 | 2. $route.name 105 | 字符串(string)。有时候,通过一个名称来标识一个路由显得更加方便,特别是在链接一个路由,或者是执行一些跳转的时候。同样,这里的name也对应了routes配置中给某个路由设置名称的name值: 106 | 要链接到一个命名路由,可以给router-link的to属性传一个对象: 107 | 108 | ``` 109 | 110 | 111 | ``` 112 | 用在调用router.push()中也是一回事: 113 | 114 | ``` 115 | this.$router.push({ name: 'Order', params: { userId: 123 }}) 116 | ``` 117 | 3. $route.params 118 | 对象(object)。路由跳转携带参数: 119 | 120 | ``` 121 | this.$router.push({ name: 'Order', params: { userId: 123 }}) 122 | console.log(this.$route.params.userId); //123 123 | ``` 124 | 4. $route.query 125 | 对象(object)。可访问携带的查询参数: 126 | 127 | ``` 128 | this.$router.push({name: 'login', query:{name: 'userName'}}); 129 | this.$route.query.name; //you 130 | //此时路由为:"http://example.com/#/login?name=userName。" 131 | ``` 132 | 5. $route.redirectedFrom 133 | 字符串(string)。重定向来源: 134 | ``` 135 | 如:{ path: '*',redirect: {name: 'hello'}} 136 | 此时访问不存在的路由http://example.com/#/a会重定向到hello, 137 | 在hello访问this.$route.redirectedFrom; 输出“/a”。 138 | ``` 139 | 6. $route.matched 140 | 数组(array)。当前路由下路由声明的所有信息,从父路由(如果有)到当前路由为止。 141 | 7. $route.hash 142 | 字符串(string)。当前路径的hash值。 143 | ### 四、vue监听$route的方式 144 | 145 | ``` 146 | watch:{‘$route’ (to, from) {}} 147 | ``` 148 | $route 作为vue实例的一个响应式属性,和在data中写的属性本质上是一样的,都可以通过this的方式拿到。既然你可以监听data中的属性变化,同样也可以监听 $route 的变化。watch中监听的对象默认回调函数中的参数值就是newVal,oldVal。作为 $route 属性来说当然也就是 to 和 from 的概念了。 149 | Vue用router.push(传参)跳转页面,参数改变,在跳转后的路由观察路由变化,进行页面刷新,可对“from->to”的过程设置动画效果。 150 | 该功能的难点就在于怎样获取“上一页”和“下一页”,即怎样分辨是“前进”还是“后退”? 151 | 例: 152 | 153 | ``` 154 | // watch $route 决定使用哪种过渡 155 | watch:{ 156 | '$route' (to, from) { 157 | //此时假设从index页面跳转到pointList页面 158 | console.log(to); // "/pointList" 159 | console.log(from); // “/index” 160 | const routeDeep = ['/', '/index','/pointList', '/settLement']; 161 | const toDepth = routeDeep.indexOf(to.path) 162 | const fromDepth = routeDeep.indexOf(from.path) 163 | this.transitionName = toDepth > fromDepth ? 'fold-left' : 'fold-right' 164 | } 165 | }, 166 | ``` 167 | to、from是最基本的路由对象,分别表示从(from)某个页面跳转到(to)另一个页面,to.path(表示要跳转到的路由地址),from.path同理。 168 | 定义routeDeep数组,将路由目录按层级依次排序(暂不考虑嵌套路由的情况),复杂单页应用里,同一层级(如同一页面上的多个导航按钮)顺序随意,然后依次排列每个导航的下一页、下下页…即保证每个“上一页”在“下一页”前面。 169 | 总结下来就是:按照routeDeep数组里定义的路由目录的顺序,“toDepth > fromDepth”表示“上一页”跳转到“下一页”,同理可由此判断是“前进”还是“后退”。 170 | ### 五、Vue2.0中transition组件的使用 171 | 172 | ``` 173 | 174 | 175 | 176 | ``` 177 | * transition中只有name属性,不可添加其他标签属性。 178 | * transition中只能有一个子元素并且该子元素需要有v-show或者v-if来控制是否显示。 179 | ##### 过渡CSS类名 180 | transition中的name属性用于 替换 vue钩子函数中的类名transitionName- 181 | * transitionName-enter: 定义进入过渡的开始状态。在元素被插入时生效,在下一个帧移除。 182 | * transitionName-enter-active: 定义进入过渡的结束状态。在元素被插入时生效,在transition/animation完成之后移除。 183 | * transitionName-leave:定义离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除。 184 | * transitionName-leave-active: 定义离开过渡的结束状态。在离开过渡被触发时生效,在transition/animation完成之后移除。 185 | ``` 186 | this.transitionName = toDepth > fromDepth ? 'fold-left' : 'fold-right' 187 | ``` 188 | 在“watch $route”中,判断页面跳转的“前进”和“后退”时,决定用不同的过渡效果(fold-left还是fold-right)。 189 | ### 六、animation、transform动画效果实现 190 | 在上一个主题中,判断页面跳转路径之后,为两种跳转的transition设置不同的类名“fold-left”、“fold-right”。 191 | 然后在CSS中,为两种类名设置不同的动画效果(这里以“左滑动”和“右滑动”为例): 192 | 193 | ``` 194 | .fold-left-enter-active { 195 | animation-name: fold-left-in; 196 | animation-duration: .3s; 197 | } 198 | .fold-left-leave-active { 199 | animation-name: fold-left-out; 200 | animation-duration: .3s; 201 | } 202 | .fold-right-enter-active { 203 | animation-name: fold-right-in; 204 | animation-duration: .3s; 205 | } 206 | .fold-right-leave-active { 207 | animation-name: fold-right-out; 208 | animation-duration: .3s; 209 | } 210 | ``` 211 | animation 属性是一个简写属性,用于设置六个动画属性: 212 | 值|描述 213 | ---|:--: 214 | animation-name|规定需要绑定到选择器的 keyframe 名称。 215 | animation-duration|规定完成动画所花费的时间,以秒或毫秒计。 216 | animation-timing-function|规定动画的速度曲线。 217 | animation-delay|规定在动画开始之前的延迟。 218 | animation-iteration-count|规定动画应该播放的次数。 219 | animation-direction|规定是否应该轮流反向播放动画。 220 | 221 | ``` 222 | @keyframes fold-left-in { 223 | 0% { 224 | transform: translate3d(100%, 0, 0); 225 | } 226 | 100% { 227 | transform: translate3d(0, 0, 0); 228 | } 229 | } 230 | @keyframes fold-left-out { 231 | 0% { 232 | transform: translate3d(0, 0, 0); 233 | } 234 | 100% { 235 | transform: translate3d(-100%, 0, 0); 236 | } 237 | } 238 | ``` 239 | 根据CSS3 @keyframes规则,创建动画。创建动画的原理即将一套CSS样式逐渐变化为另一套样式。在动画过程中,能够多次改变这套CSS样式。可以“百分比”来规定改变发生的时间,或者通过关键词“from”和“to”,等价于“0%”(动画的开始时间)和“100%”(动画的结束时间)。一般为了获得最佳的浏览器支持,应该始终定义0%和100%选择器。 240 | transform属性向元素应用2D或3D转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜。translate3d(x,y,z)定义3D转换,如transform:translate3d(100%, 0, 0)只改变了x的值,即代表横向左滑动,同理可相应推出其他情况。 241 | ## 总结 242 | 以上就是vue页面跳转动画效果功能实现的6个步骤,即这个功能中所含括的6个大知识点,当然其中还包括许多扩展的知识点,学无止境,需慢慢深入挖掘… 243 | --------------------------------------------------------------------------------