├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── example ├── Ball.vue ├── CustomCom.vue ├── Dna.vue ├── Fullscreen.vue ├── Multiple.vue ├── ball.html ├── ball.js ├── dna.html ├── dna.js ├── fullscreen.js ├── index.html ├── logo.png ├── multiple.html ├── multiple.js └── utils.js ├── package.json ├── src ├── components │ ├── Step │ │ ├── index.js │ │ ├── index.vue │ │ ├── step.js │ │ ├── step.pug │ │ └── step.scss │ └── Viewport │ │ ├── index.js │ │ ├── index.vue │ │ ├── viewport.js │ │ ├── viewport.pug │ │ └── viewport.scss ├── index.js └── utils │ ├── computeScale.js │ ├── debounce.js │ ├── index.js │ ├── initStepData.js │ ├── reverseData.js │ └── toNumber.js ├── test ├── e2e │ ├── custom-assertions │ │ └── elementCount.js │ ├── nightwatch.conf.js │ ├── runner.js │ └── specs │ │ └── test.js └── unit │ ├── .eslintrc │ ├── index.js │ ├── karma.conf.js │ └── specs │ ├── components │ └── viewport.spec.js │ ├── utils.spec.js │ └── utils │ ├── computeScale.spec.js │ ├── debounce.spec.js │ ├── initData.spec.js │ └── toNumber.spec.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": ["transform-runtime", "transform-vue-jsx"], 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 | dist/*.js 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | extends: 'airbnb-base', 8 | // required to lint *.vue files 9 | plugins: [ 10 | 'html' 11 | ], 12 | env: { 13 | browser: true, 14 | }, 15 | // check if imports actually resolve 16 | 'settings': { 17 | 'import/resolver': { 18 | 'webpack': { 19 | 'config': 'build/webpack.base.conf.js' 20 | } 21 | } 22 | }, 23 | // add your custom rules here 24 | 'rules': { 25 | // don't require .vue extension when importing 26 | 'import/extensions': ['error', 'always', { 27 | 'js': 'never', 28 | 'vue': 'never' 29 | }], 30 | // allow debugger during development 31 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 32 | 'import/no-extraneous-dependencies': ['error', {devDependencies: true, optionalDependencies: false, peerDependencies: false}], 33 | 'no-console': 0, 34 | semi: 0, 35 | 'no-underscore-dangle': ['error', { 36 | allowAfterThis: true, 37 | allowAfterSuper: true, 38 | }], 39 | 'no-param-reassign': 0, 40 | 'global-require': 0, 41 | 'import/no-dynamic-require': 0, 42 | 'import/prefer-default-export': 0, 43 | 'prefer-arrow-callback': 0, 44 | 'comma-dangle': 0, 45 | 'no-unused-vars': ['error', { argsIgnorePattern: 'h' }], 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | test/unit/coverage 4 | dist/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example/ 2 | node_modules/ 3 | src/ 4 | test/ 5 | config/ 6 | build/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-impress 2 | 3 | * * * 4 | 5 | Inspired by [impress.js](https://github.com/impress/impress.js), thanks for it so much. 6 | 7 | At the end of impress.js, the words *use the source, Luke!* really encouraged me! Thanks *star wars* too. I read the source with the power and made this vue component. 8 | 9 | ## [demo](http://superwf.github.io/vue-impress-demo.html) 10 | 11 | ## Compatibility 12 | 13 | Compatible with vue 2.x 14 | 15 | ## install 16 | 17 | ``` 18 | npm install vue-impress 19 | ``` 20 | 21 | ## usage 22 | 23 | Define a Vue component file, then mount it. 24 | Do not forget the css file. 25 | ```javascript 26 | 27 | 31 | 32 | 116 | 117 | 144 | 145 | ``` 146 | 147 | ## API 148 | 149 | ### vue component 150 | 151 | only one component `impress-viewport` 152 | 153 | normally impress-viewport has no child 154 | but it can contain other component 155 | Check the [ball example](https://github.com/superwf/vue-impress/blob/master/example/Ball.vue) 156 | 157 | ### Props 158 | 159 | | name | type | 160 | | ---- | ---- | 161 | | config | Object | 162 | | steps | [Object] | 163 | 164 | #### the `config` prop 165 | 166 | | key | type | description | 167 | | --- | ---- | ---- | 168 | | width | Number | required, use for compute scale ratio | 169 | | height | Number | required, use for compute scale ratio| 170 | | transitionDuration | Number | optional, default 1000, unit ms, duration time between step animation | 171 | | transitionTimingFunction | String | optional, default 'ease', css3 transition-timing-function used when change step | 172 | | perspective | Number | optional, default 1000, the distance to generate 3d style | 173 | | fullscreen | Boolean | optional, default true | 174 | 175 | When `fullscreen` is true, it means that there should be only one instance in current page. vue-impress will use config width and height and window innerWidth, innerHeight to compute scale. 176 | When `fullscreen` is false, the vue-impress parent element should has has a absolute or relative position, and has a explicit width and height 177 | 178 | 179 | #### the object in `steps` array prop 180 | 181 | | key | type | description | 182 | | --- | ---- | ---- | 183 | | x | Number | optional, default 0, translate x position | 184 | | y | Number | optional, default 0, translate y position | 185 | | z | Number | optional, default 0, translate z position | 186 | | rotateX | Number | optional, default 0, rotate deg by x axis | 187 | | rotateY | Number | optional, default 0, rotate deg by y axis | 188 | | rotateZ | Number | optional, default 0, rotate deg by z axis | 189 | | rotate | Number | optional, default 0, the same as rotateZ | 190 | | rotateOrder | [String] | optional, default ['x', 'y', 'z'] the rotate order, it matters when rotate more than one direction | 191 | | scale | Number | default 1 | 192 | | transitionDuration | Number | optional, unit ms, if this exists in step, it will overwrite `transitionDuration` in config prop, just for this step | 193 | | transitionTimingFunction | String | optional, default use the property in config, you can define it in each step | 194 | | content | String | optional, string content to show | 195 | | component | Object | optional, your custom component, when component exists, content is needless | 196 | | props | Object | optional, the props your component will use | 197 | | id | String | optional, step identity, when step is active, the outer wrapper will add this id to classList. if not provided, `step-${stepIndex}` will be used. it is useful when some step is active and need a special css. For example .impress-viewport.step-0, or .impress-viewport.overview | 198 | 199 | ### Events 200 | 201 | | name | description | 202 | | --- | ---- | 203 | | `impress:stepenter` | triggered when the step is in active, with param step index | 204 | | `impress:stepleave` | triggered when step leave active, with param step index | 205 | 206 | ### instance methods 207 | 208 | | name | param| description | 209 | | --- | ---- | ---- | 210 | | gotoStep | index | when the step is in active | 211 | | nextStep | | goto next step, same as gotoStep( index + 1 ), goto first step when current is last step | 212 | | prevStep | | goto prev step, same as gotoStep( index - 1 ), goto last step when current is first step | 213 | 214 | ## element class, used for css style 215 | 216 | | name | description | 217 | | --- | --- | 218 | | impress-viewport | first wrapper, for 3d perspective | 219 | | impress-canvas | second wrapper, fly to active step when step changes | 220 | | impress-step | each step class, the default font-size is 30px, you can overwrite it by your css | 221 | 222 | 223 | Check the [example](https://github.com/superwf/vue-impress/blob/master/example/Fullscreen.vue) and read the comment there 224 | 225 | ``` 226 | git clone git@github.com:superwf/vue-impress.git 227 | 228 | cd vue-impress 229 | 230 | yarn install 231 | 232 | npm run dev 233 | ``` 234 | 235 | fullscreen example http://127.0.0.1:8080 236 | multiple instance http://127.0.0.1:8080/multiple.html 237 | dna instance http://127.0.0.1:8080/dna.html 238 | ball instance http://127.0.0.1:8080/ball.html 239 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | require('./check-versions')() 3 | require('shelljs/global') 4 | env.NODE_ENV = 'production' 5 | 6 | var path = require('path') 7 | var config = require('../config') 8 | var ora = require('ora') 9 | var webpack = require('webpack') 10 | var webpackConfig = require('./webpack.prod.conf') 11 | // console.log(webpackConfig); 12 | 13 | console.log( 14 | ' Tip:\n' + 15 | ' Built files are meant to be served over an HTTP server.\n' + 16 | ' Opening index.html over file:// won\'t work.\n' 17 | ) 18 | 19 | var spinner = ora('building for production...') 20 | spinner.start() 21 | 22 | // var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 23 | // rm('-rf', assetsPath) 24 | // mkdir('-p', assetsPath) 25 | // cp('-R', 'static/*', assetsPath) 26 | 27 | webpack(webpackConfig, function (err, stats) { 28 | spinner.stop() 29 | if (err) throw err 30 | process.stdout.write(stats.toString({ 31 | colors: true, 32 | modules: false, 33 | children: false, 34 | chunks: false, 35 | chunkModules: false 36 | }) + '\n') 37 | }) 38 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var semver = require('semver') 2 | var chalk = require('chalk') 3 | var packageConfig = require('../package.json') 4 | var exec = function (cmd) { 5 | return require('child_process') 6 | .execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | { 16 | name: 'npm', 17 | currentVersion: exec('npm --version'), 18 | versionRequirement: packageConfig.engines.npm 19 | } 20 | ] 21 | 22 | module.exports = function () { 23 | var warnings = [] 24 | for (var i = 0; i < versionRequirements.length; i++) { 25 | var mod = versionRequirements[i] 26 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 27 | warnings.push(mod.name + ': ' + 28 | chalk.red(mod.currentVersion) + ' should be ' + 29 | chalk.green(mod.versionRequirement) 30 | ) 31 | } 32 | } 33 | 34 | if (warnings.length) { 35 | console.log('') 36 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 37 | console.log() 38 | for (var i = 0; i < warnings.length; i++) { 39 | var warning = warnings[i] 40 | console.log(' ' + warning) 41 | } 42 | console.log() 43 | process.exit(1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | var config = require('../config') 3 | if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 4 | var path = require('path') 5 | var express = require('express') 6 | var webpack = require('webpack') 7 | var opn = require('opn') 8 | var proxyMiddleware = require('http-proxy-middleware') 9 | var webpackConfig = process.env.NODE_ENV === 'testing' 10 | ? require('./webpack.prod.conf') 11 | : require('./webpack.dev.conf') 12 | 13 | // default port where dev server listens for incoming traffic 14 | var port = process.env.PORT || config.dev.port 15 | // Define HTTP proxies to your custom API backend 16 | // https://github.com/chimurai/http-proxy-middleware 17 | var proxyTable = config.dev.proxyTable 18 | 19 | var app = express() 20 | var compiler = webpack(webpackConfig) 21 | 22 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 23 | publicPath: webpackConfig.output.publicPath, 24 | stats: { 25 | colors: true, 26 | chunks: false 27 | } 28 | }) 29 | 30 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 31 | // force page reload when html-webpack-plugin template changes 32 | compiler.plugin('compilation', function (compilation) { 33 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 34 | hotMiddleware.publish({ action: 'reload' }) 35 | cb() 36 | }) 37 | }) 38 | 39 | // proxy api requests 40 | Object.keys(proxyTable).forEach(function (context) { 41 | var options = proxyTable[context] 42 | if (typeof options === 'string') { 43 | options = { target: options } 44 | } 45 | app.use(proxyMiddleware(context, options)) 46 | }) 47 | 48 | // handle fallback for HTML5 history API 49 | app.use(require('connect-history-api-fallback')()) 50 | 51 | // serve webpack bundle output 52 | app.use(devMiddleware) 53 | 54 | // enable hot-reload and state-preserving 55 | // compilation error display 56 | app.use(hotMiddleware) 57 | 58 | // serve pure static assets 59 | // var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 60 | app.use(express.static('./example')) 61 | 62 | module.exports = app.listen(port, function (err) { 63 | if (err) { 64 | console.log(err) 65 | return 66 | } 67 | var uri = 'http://localhost:' + port 68 | console.log('Listening at ' + uri + '\n') 69 | 70 | // when env is testing, don't need open it 71 | if (process.env.NODE_ENV !== 'testing') { 72 | opn(uri) 73 | } 74 | }) 75 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | // generate loader string to be used with extract text plugin 15 | function generateLoaders (loaders) { 16 | var sourceLoader = loaders.map(function (loader) { 17 | var extraParamChar 18 | if (/\?/.test(loader)) { 19 | loader = loader.replace(/\?/, '-loader?') 20 | extraParamChar = '&' 21 | } else { 22 | loader = loader + '-loader' 23 | extraParamChar = '?' 24 | } 25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 26 | }).join('!') 27 | 28 | // Extract CSS when that option is specified 29 | // (which is the case during production build) 30 | if (options.extract) { 31 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 32 | } else { 33 | return ['vue-style-loader', sourceLoader].join('!') 34 | } 35 | } 36 | 37 | // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html 38 | return { 39 | css: generateLoaders(['css']), 40 | postcss: generateLoaders(['css']), 41 | less: generateLoaders(['css', 'less']), 42 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 43 | scss: generateLoaders(['css', 'sass']), 44 | stylus: generateLoaders(['css', 'stylus']), 45 | styl: generateLoaders(['css', 'stylus']) 46 | } 47 | } 48 | 49 | // Generate loaders for standalone style files (outside of .vue) 50 | exports.styleLoaders = function (options) { 51 | var output = [] 52 | var loaders = exports.cssLoaders(options) 53 | for (var extension in loaders) { 54 | var loader = loaders[extension] 55 | output.push({ 56 | test: new RegExp('\\.' + extension + '$'), 57 | loader: loader 58 | }) 59 | } 60 | return output 61 | } 62 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var projectRoot = path.resolve(__dirname, '../') 5 | 6 | var env = process.env.NODE_ENV 7 | // check env & config/index.js to decide whether to enable CSS source maps for the 8 | // various preprocessor loaders added to vue-loader at the end of this file 9 | var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap) 10 | var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap) 11 | var useCssSourceMap = cssSourceMapDev || cssSourceMapProd 12 | 13 | module.exports = { 14 | entry: { 15 | fullscreen: './example/fullscreen.js', 16 | multiple: './example/multiple.js', 17 | dna: './example/dna.js', 18 | ball: './example/ball.js', 19 | }, 20 | output: { 21 | path: config.build.assetsRoot, 22 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, 23 | filename: '[name].js' 24 | }, 25 | resolve: { 26 | extensions: ['', '.js', '.vue', '.json'], 27 | fallback: [path.join(__dirname, '../node_modules')], 28 | alias: { 29 | 'vue$': 'vue/dist/vue.common.js', 30 | 'src': path.resolve(__dirname, '../src'), 31 | 'assets': path.resolve(__dirname, '../src/assets'), 32 | 'components': path.resolve(__dirname, '../src/components') 33 | } 34 | }, 35 | resolveLoader: { 36 | fallback: [path.join(__dirname, '../node_modules')] 37 | }, 38 | module: { 39 | preLoaders: [ 40 | { 41 | test: /\.vue$/, 42 | loader: 'eslint', 43 | include: projectRoot, 44 | exclude: /node_modules/ 45 | }, 46 | { 47 | test: /\.js$/, 48 | loader: 'eslint', 49 | include: projectRoot, 50 | exclude: /node_modules/ 51 | } 52 | ], 53 | loaders: [ 54 | { 55 | test: /\.vue$/, 56 | loader: 'vue' 57 | }, 58 | { 59 | test: /.scss$/, 60 | loader: 'style!css!sass', 61 | }, 62 | { 63 | test: /\.js$/, 64 | loader: 'babel', 65 | include: projectRoot, 66 | exclude: /node_modules/ 67 | }, 68 | { 69 | test: /\.pug/, 70 | loader: 'pug', 71 | }, 72 | { 73 | test: /\.json$/, 74 | loader: 'json' 75 | }, 76 | { 77 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 78 | loader: 'url', 79 | query: { 80 | limit: 10000, 81 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 82 | } 83 | }, 84 | { 85 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 86 | loader: 'url', 87 | query: { 88 | limit: 10000, 89 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 90 | } 91 | } 92 | ] 93 | }, 94 | eslint: { 95 | formatter: require('eslint-friendly-formatter') 96 | }, 97 | vue: { 98 | loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }), 99 | postcss: [ 100 | require('autoprefixer')({ 101 | browsers: ['last 2 versions'] 102 | }) 103 | ] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var config = require('../config') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var utils = require('./utils') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | 8 | // add hot-reload related code to entry chunks 9 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 10 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 11 | }) 12 | 13 | module.exports = merge(baseWebpackConfig, { 14 | module: { 15 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 16 | }, 17 | // eval-source-map is faster for development 18 | devtool: '#eval-source-map', 19 | plugins: [ 20 | new webpack.DefinePlugin({ 21 | 'process.env': config.dev.env 22 | }), 23 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 24 | new webpack.optimize.OccurrenceOrderPlugin(), 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | favicon: './example/logo.png', 31 | template: './example/index.html', 32 | inject: false, 33 | }), 34 | new HtmlWebpackPlugin({ 35 | filename: 'multiple.html', 36 | favicon: './example/logo.png', 37 | template: './example/multiple.html', 38 | inject: false, 39 | }), 40 | new HtmlWebpackPlugin({ 41 | filename: 'dna.html', 42 | favicon: './example/logo.png', 43 | template: './example/dna.html', 44 | inject: false, 45 | }), 46 | new HtmlWebpackPlugin({ 47 | filename: 'ball.html', 48 | favicon: './example/logo.png', 49 | template: './example/ball.html', 50 | inject: false, 51 | }), 52 | ] 53 | }) 54 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 6 | var merge = require('webpack-merge') 7 | var baseWebpackConfig = require('./webpack.base.conf') 8 | var env = 'production' 9 | 10 | const version = process.env.VERSION || require('../package.json').version 11 | 12 | delete baseWebpackConfig.entry 13 | 14 | var webpackConfig = merge(baseWebpackConfig, { 15 | entry: { 16 | 'vue-impress': './src/index.js', 17 | }, 18 | module: { 19 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) 20 | }, 21 | devtool: config.build.productionSourceMap ? '#source-map' : false, 22 | output: { 23 | path: config.build.assetsRoot, 24 | filename: '[name].js', 25 | library: 'VueImpress', 26 | libraryTarget: 'umd', 27 | }, 28 | vue: { 29 | loaders: { 30 | css: ExtractTextPlugin.extract('css'), 31 | scss: ExtractTextPlugin.extract('css!sass'), 32 | }, 33 | postcss: [ 34 | require('autoprefixer')({ 35 | browsers: ['last 2 versions'], 36 | }) 37 | ], 38 | }, 39 | externals: { 40 | vue: { 41 | commonjs: 'vue', 42 | commonjs2: 'vue', 43 | amd: 'vue', 44 | root: 'Vue', 45 | var: 'Vue', 46 | }, 47 | }, 48 | plugins: [ 49 | new webpack.optimize.DedupePlugin(), 50 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 51 | new webpack.DefinePlugin({ 52 | 'process.env': env 53 | }), 54 | new webpack.optimize.UglifyJsPlugin({ 55 | compress: { 56 | warnings: false 57 | }, 58 | comments: false, 59 | }), 60 | new ExtractTextPlugin('[name].css'), 61 | new webpack.optimize.OccurrenceOrderPlugin(), 62 | new webpack.BannerPlugin( 63 | `/*! 64 | * vue-impress v${version} 65 | * Released under the MIT License. 66 | */` 67 | , { 68 | raw: true, 69 | entryOnly: true, 70 | }), 71 | ] 72 | }) 73 | 74 | if (config.build.productionGzip) { 75 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 76 | 77 | webpackConfig.plugins.push( 78 | new CompressionWebpackPlugin({ 79 | asset: '[path].gz[query]', 80 | algorithm: 'gzip', 81 | test: new RegExp( 82 | '\\.(' + 83 | config.build.productionGzipExtensions.join('|') + 84 | ')$' 85 | ), 86 | threshold: 10240, 87 | minRatio: 0.8 88 | }) 89 | ) 90 | } 91 | 92 | module.exports = webpackConfig 93 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | assetsRoot: path.resolve(__dirname, '../dist'), 8 | assetsSubDirectory: 'static', 9 | assetsPublicPath: '/', 10 | productionSourceMap: true, 11 | // Gzip off by default as many popular static hosts such as 12 | // Surge or Netlify already gzip all static assets for you. 13 | // Before setting to `true`, make sure to: 14 | // npm install --save-dev compression-webpack-plugin 15 | productionGzip: false, 16 | productionGzipExtensions: ['js', 'css'] 17 | }, 18 | dev: { 19 | env: require('./dev.env'), 20 | port: 8080, 21 | assetsSubDirectory: 'example', 22 | assetsPublicPath: '/', 23 | proxyTable: {}, 24 | // CSS Sourcemaps off by default because relative paths are "buggy" 25 | // with this option, according to the CSS-Loader README 26 | // (https://github.com/webpack/css-loader#sourcemaps) 27 | // In our experience, they generally work as expected, 28 | // just be aware of this issue when enabling this option. 29 | cssSourceMap: false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /example/Ball.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 154 | 155 | 190 | -------------------------------------------------------------------------------- /example/CustomCom.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 31 | -------------------------------------------------------------------------------- /example/Dna.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 81 | 82 | 105 | -------------------------------------------------------------------------------- /example/Fullscreen.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 98 | 99 | 120 | -------------------------------------------------------------------------------- /example/Multiple.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 89 | 90 | 115 | -------------------------------------------------------------------------------- /example/ball.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue impress.js ball example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/ball.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Ball from './Ball.vue' 3 | 4 | new Vue(Ball).$mount('#app') 5 | -------------------------------------------------------------------------------- /example/dna.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue impress.js dna example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/dna.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Dna from './Dna.vue' 3 | 4 | new Vue(Dna).$mount('#app') 5 | -------------------------------------------------------------------------------- /example/fullscreen.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Fullscreen from './Fullscreen.vue' 3 | 4 | new Vue(Fullscreen).$mount('#app') 5 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue impress.js fullscreen example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superwf/vue-impress/8d839da94f77f009388fdc76916033d66b28fb06/example/logo.png -------------------------------------------------------------------------------- /example/multiple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue impress.js multiple example 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/multiple.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Multiple from './Multiple.vue' 3 | 4 | new Vue(Multiple).$mount('#app1') 5 | -------------------------------------------------------------------------------- /example/utils.js: -------------------------------------------------------------------------------- 1 | function getOffset(element) { 2 | let actualTop = element.offsetTop 3 | let actualLeft = element.offsetLeft 4 | let current = element.offsetParent 5 | while (current !== null) { 6 | actualTop += current.offsetTop 7 | actualLeft += current.offsetLeft 8 | current = current.offsetParent 9 | } 10 | const elementScrollTop = document.documentElement.scrollTop 11 | const elementScrollLeft = document.documentElement.scrollLeft 12 | 13 | return { 14 | top: actualTop - elementScrollTop, 15 | left: actualLeft - elementScrollLeft, 16 | } 17 | } 18 | 19 | export { getOffset } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-impress", 3 | "version": "0.0.7", 4 | "description": "vue impress component", 5 | "main": "dist/vue-impress.js", 6 | "scripts": { 7 | "dev": "node build/dev-server.js", 8 | "test": "npm run unit", 9 | "build": "NODE_ENV=production node build/build.js", 10 | "prepublish": "npm run build", 11 | "unit": "karma start test/unit/karma.conf.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/superwf/vue-impress.git" 16 | }, 17 | "author": "superwf@gmail.com", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/superwf/vue-impress/issues" 21 | }, 22 | "homepage": "https://github.com/superwf/vue-impress#readme", 23 | "dependencies": { 24 | "vue": "^2.1.10" 25 | }, 26 | "devDependencies": { 27 | "babel": "^6.5.2", 28 | "babel-cli": "^6.22.2", 29 | "babel-eslint": "^7.1.1", 30 | "babel-helper-vue-jsx-merge-props": "^2.0.2", 31 | "babel-loader": "^6.2.10", 32 | "babel-plugin-external-helpers": "^6.22.0", 33 | "babel-plugin-syntax-jsx": "^6.18.0", 34 | "babel-plugin-transform-runtime": "^6.15.0", 35 | "babel-plugin-transform-vue-jsx": "^3.3.0", 36 | "babel-preset-es2015": "^6.18.0", 37 | "babel-preset-stage-0": "^6.16.0", 38 | "babel-register": "^6.18.0", 39 | "chalk": "^1.1.3", 40 | "compression-webpack-plugin": "^0.3.2", 41 | "connect-history-api-fallback": "^1.3.0", 42 | "css-loader": "^0.26.1", 43 | "es6-promise": "^4.0.5", 44 | "eslint": "^3.13.1", 45 | "eslint-config-airbnb-base": "^11.0.1", 46 | "eslint-friendly-formatter": "^2.0.7", 47 | "eslint-loader": "^1.6.1", 48 | "eslint-plugin-html": "^1.7.0", 49 | "eslint-plugin-import": "^2.2.0", 50 | "eventsource-polyfill": "^0.9.6", 51 | "expect": "^1.20.2", 52 | "express": "^4.14.0", 53 | "extract-text-webpack-plugin": "^1.0.1", 54 | "function-bind": "^1.1.0", 55 | "hammerjs": "^2.0.8", 56 | "html-webpack-plugin": "^2.26.0", 57 | "http-proxy-middleware": "^0.17.3", 58 | "isparta-loader": "^2.0.0", 59 | "karma": "^1.4.0", 60 | "karma-chrome-launcher": "^2.0.0", 61 | "karma-coverage": "^1.1.1", 62 | "karma-expect": "^1.1.3", 63 | "karma-mocha": "^1.3.0", 64 | "karma-phantomjs-launcher": "^1.0.2", 65 | "karma-sourcemap-loader": "^0.3.7", 66 | "karma-spec-reporter": "^0.0.26", 67 | "karma-webpack": "^2.0.1", 68 | "lolex": "^1.5.2", 69 | "mocha": "^3.2.0", 70 | "node-sass": "^4.3.0", 71 | "opn": "^4.0.2", 72 | "optimize-js-plugin": "^0.0.4", 73 | "ora": "^1.1.0", 74 | "pug": "^2.0.0-beta6", 75 | "pug-loader": "^2.3.0", 76 | "sass-loader": "^4.1.1", 77 | "semver": "^5.3.0", 78 | "style-loader": "^0.13.1", 79 | "url-loader": "^0.5.7", 80 | "vue-loader": "^10.0.2", 81 | "vue-style-loader": "^1.0.0", 82 | "vue-template-compiler": "^2.1.10", 83 | "webpack": "^1.14.0", 84 | "webpack-dev-middleware": "^1.9.0", 85 | "webpack-hot-middleware": "^2.15.0", 86 | "webpack-merge": "^2.4.0" 87 | }, 88 | "engines": { 89 | "node": ">= 4.0.0", 90 | "npm": ">= 3.0.0" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/components/Step/index.js: -------------------------------------------------------------------------------- 1 | import step from './index.vue' 2 | 3 | export default step 4 | -------------------------------------------------------------------------------- /src/components/Step/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Step/step.js: -------------------------------------------------------------------------------- 1 | import { translate, rotate, scale } from '../../utils' 2 | 3 | export default { 4 | props: ['step', 'index', 'active', 'present'], 5 | 6 | methods: { 7 | click() { 8 | this.$parent.$emit('impress:goto', this.index) 9 | }, 10 | }, 11 | 12 | computed: { 13 | style() { 14 | const { step } = this 15 | const transform = `${translate(step.translate)} 16 | ${rotate(step.rotate)} ${scale(step.scale)}` 17 | return { transform } 18 | }, 19 | }, 20 | 21 | render(h) { 22 | const { step, style, click, active, present } = this 23 | const content = step.component ? 24 | : step.content 25 | return
26 | { content } 27 |
28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Step/step.pug: -------------------------------------------------------------------------------- 1 | // .impress-step(":style"="style", @click="click(index)", :class="{active: active, present: present}") 2 | // template(v-if="contentIsString") 3 | // | {{ step.content }} 4 | // div(v-else-if="contentIsVue", ref="mountPoint") 5 | // component(v-else, :is="step.content") 6 | -------------------------------------------------------------------------------- /src/components/Step/step.scss: -------------------------------------------------------------------------------- 1 | .impress-step { 2 | font-size: 30px; 3 | transition: opacity 1s; 4 | position: absolute; 5 | transform-origin: center center 0px; 6 | transform-style: preserve-3d; 7 | will-change: opactiy; 8 | opacity: 0.3; 9 | 10 | &.active { 11 | opacity: 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Viewport/index.js: -------------------------------------------------------------------------------- 1 | import viewport from './index.vue' 2 | 3 | export default viewport 4 | -------------------------------------------------------------------------------- /src/components/Viewport/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Viewport/viewport.js: -------------------------------------------------------------------------------- 1 | import debounce from '../../utils/debounce' 2 | import Step from '../Step' 3 | import { translate, scale, rotate, transitionDuration, impressSupported } from '../../utils' 4 | import initStepData from '../../utils/initStepData' 5 | import reverseData from '../../utils/reverseData' 6 | import computeScale from '../../utils/computeScale' 7 | 8 | export default { 9 | props: { 10 | /* steps param array 11 | * 各动画的位置参数 */ 12 | steps: { 13 | type: Array, 14 | required: true, 15 | }, 16 | 17 | config: { 18 | type: Object, 19 | }, 20 | 21 | }, 22 | 23 | components: { 24 | Step, 25 | }, 26 | 27 | methods: { 28 | nextStep() { 29 | const stepIndex = (this.stepIndex >= this.steps.length - 1) ? 30 | 0 : this.stepIndex + 1 31 | this.gotoStep(stepIndex) 32 | }, 33 | prevStep() { 34 | const stepIndex = this.stepIndex <= 0 ? this.steps.length - 1 : this.stepIndex - 1 35 | this.gotoStep(stepIndex) 36 | }, 37 | 38 | gotoStep(stepIndex) { 39 | /* trigger when leave step, like impress.js 40 | * 当离开当前step时即触发,不用等下一个stepenter所以不用setTimeout 41 | * impress.js就是这么处理的,先这样 */ 42 | if (stepIndex !== this.stepIndex) { 43 | this.$emit('impress:stepleave', this.stepIndex) 44 | } 45 | this.stepIndex = stepIndex 46 | const currentData = this.stepsData[stepIndex] 47 | this.stepClass = currentData.id 48 | const target = reverseData(currentData) 49 | const duration = currentData.transitionDuration || this.transitionDuration 50 | const timingFunction = currentData.transitionTimingFunction || 51 | this.config.transitionTimingFunction || 'ease' 52 | 53 | /* default perspective 1000px */ 54 | const perspective = `${(this.config.perspective || 1000) / this.containerScale}px` 55 | 56 | this.initialRootStyle = { 57 | /* perspective is for the elements in step which have transform css setting 58 | * if there are no transform elements, perspective is meaningless 59 | * 一开始没看出用处,后来发现perspective是给每个step里设置了transform的元素产生视角用的,若step中没有3d变换的元素是没什么用 */ 60 | perspective, 61 | transitionDuration: duration, 62 | transform: scale(target.scale * this.containerScale), 63 | } 64 | this.canvasStyle = { 65 | transitionDuration: duration, 66 | transitionTimingFunction: timingFunction, 67 | transform: rotate(target.rotate) + translate(target.translate), 68 | } 69 | /* when switch steps too quickly, 70 | * only after the duration time between steps, trigger `impress:stepenter` event 71 | * 当快速切换step时,超过duration时间之后的step才触发impress:stepenter */ 72 | clearTimeout(this.stepEnterTimeout) 73 | this.stepEnterTimeout = setTimeout(() => { 74 | this.$emit('impress:stepenter', this.stepIndex) 75 | }, transitionDuration(duration, true)) 76 | }, 77 | }, 78 | 79 | beforeMount() { 80 | this.impressSupported = impressSupported 81 | this.stepsData = this.steps.map((data, i) => { 82 | const stepData = initStepData(data) 83 | stepData.content = data.content 84 | stepData.props = data.props 85 | stepData.id = data.id || `step-${i}` 86 | stepData.component = data.component 87 | return stepData 88 | }) 89 | if (this.config.fullscreen !== false) { 90 | this.config.fullscreen = true 91 | } 92 | this.transitionDuration = transitionDuration(this.config.transitionDuration || 1000) 93 | }, 94 | 95 | mounted() { 96 | this.$on('impress:goto', (index) => { 97 | this.gotoStep(index) 98 | }) 99 | if (this.config.fullscreen) { 100 | this.resize = debounce(() => { 101 | this.containerScale = computeScale({ 102 | width: window.innerWidth, 103 | height: window.innerHeight, 104 | }, this.config) 105 | this.gotoStep(this.stepIndex) 106 | }, 250) 107 | this.containerScale = computeScale({ 108 | width: window.innerWidth, 109 | height: window.innerHeight, 110 | }, this.config) 111 | window.addEventListener('resize', this.resize) 112 | } else { 113 | const parent = this.$refs.root.offsetParent 114 | this.containerScale = computeScale({ 115 | width: parent.clientWidth, 116 | height: parent.clientHeight, 117 | }, this.config) 118 | } 119 | this.gotoStep(0) 120 | }, 121 | 122 | beforeDestroy() { 123 | if (this.config.fullscreen) { 124 | window.removeEventListener('resize', this.resize) 125 | } 126 | }, 127 | 128 | data() { 129 | return { 130 | impressSupported: true, 131 | initialRootStyle: null, 132 | canvasStyle: null, 133 | stepIndex: null, 134 | lastStepIndex: null, 135 | stepsData: [], 136 | transitionDuration: '', 137 | /* for debounce impress:stepenter event in steps duration time 138 | * 给每个step enter之后的timeout用 */ 139 | stepEnterTimeout: null, 140 | containerScale: null, 141 | stepClass: '', 142 | } 143 | }, 144 | } 145 | -------------------------------------------------------------------------------- /src/components/Viewport/viewport.pug: -------------------------------------------------------------------------------- 1 | .impress-viewport(ref="root", ":style"="initialRootStyle", :class="stepClass") 2 | div(v-if="!impressSupported") your browser does not support this component 3 | .impress-canvas(:style="canvasStyle" v-else="true") 4 | step(v-for="(step, i) in stepsData", :step="step", :index="i", :active="stepIndex === i") 5 | slot 6 | -------------------------------------------------------------------------------- /src/components/Viewport/viewport.scss: -------------------------------------------------------------------------------- 1 | .impress-viewport { 2 | transition-property: transform, perspective; 3 | will-change: transform, perspective; 4 | } 5 | .impress-canvas { 6 | will-change: transform; 7 | transition-property: transform; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | } 12 | 13 | .impress-viewport, .impress-canvas { 14 | height: 100%; 15 | width: 100%; 16 | transform-origin: center center 0px; 17 | transform-style: preserve-3d; 18 | position: absolute; 19 | } 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Viewport from './components/Viewport' 2 | 3 | export default { 4 | install(Vue) { 5 | Vue.component('impress-viewport', Viewport) 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/computeScale.js: -------------------------------------------------------------------------------- 1 | // `computeScale` counts the scale factor between container size and size 2 | // defined for the presentation in the config. 3 | function computeScale(container, config) { 4 | const hScale = container.height / config.height 5 | const wScale = container.width / config.width 6 | return hScale > wScale ? wScale : hScale 7 | } 8 | 9 | export default computeScale 10 | -------------------------------------------------------------------------------- /src/utils/debounce.js: -------------------------------------------------------------------------------- 1 | /* debounce function calls within delay */ 2 | function debounce(fn, delay) { 3 | let timer = null 4 | return function debounced(...args) { 5 | clearTimeout(timer) 6 | timer = setTimeout(() => { 7 | fn(...args) 8 | }, delay) 9 | } 10 | } 11 | 12 | export default debounce 13 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* `translate` builds a translate transform string for given data. */ 2 | export function translate(t) { 3 | return ` translate3d(${t.x}px, ${t.y}px, ${t.z}px)` 4 | } 5 | 6 | /* `rotate` builds a rotate transform string for given data. 7 | * By default the rotations are in X Y Z order that can be reverted by passing `true` 8 | * as second parameter. 9 | * 我没明白revert有什么用,但是还是加上了 10 | */ 11 | export function rotate(r) { 12 | const r1 = { 13 | x: ` rotateX(${r.x}deg)`, 14 | y: ` rotateY(${r.y}deg)`, 15 | z: ` rotateZ(${r.z}deg)`, 16 | } 17 | const order = r.order 18 | return r1[order[0]] + r1[order[1]] + r1[order[2]] 19 | } 20 | 21 | /* `scale` builds a scale transform string for given data. */ 22 | export function scale(s) { 23 | return ` scale(${s})` 24 | } 25 | 26 | const dummy = document.createElement('dummy') 27 | 28 | /* add css prefix, copy from impress.js' 29 | * 只在验证浏览器支持中用一下,vue本身会管css的前缀问题 30 | **/ 31 | export const pfx = (function prefix() { 32 | const style = dummy.style 33 | const prefixes = 'webkit moz O ms Khtml'.split(' ') 34 | const memory = {} 35 | 36 | return function withPrefix(prop) { 37 | if (typeof memory[prop] === 'undefined') { 38 | const ucProp = prop.charAt(0).toUpperCase() + prop.substr(1) 39 | const props = (`${prop} ${prefixes.join(`${ucProp} `)}${ucProp}`).split(' ') 40 | 41 | memory[prop] = null 42 | for (let i = 0; i < props.length; i += 1) { 43 | if (style[props[i]] !== undefined) { 44 | memory[prop] = props[i] 45 | break 46 | } 47 | } 48 | } 49 | 50 | return memory[prop] 51 | } 52 | }()) 53 | 54 | /* from time unit `ms` to `s` for transition-duration 55 | * when revert is false transform 1000 to 1s 56 | * when revert is true transform 1s to 1000 57 | * */ 58 | export const transitionDuration = (duration, revert = false) => (revert ? duration.slice(0, duration.length - 1) * 1000 : `${duration / 1000}s`) 59 | 60 | 61 | // CHECK SUPPORT 62 | const ua = navigator.userAgent.toLowerCase() 63 | export const impressSupported = 64 | 65 | // Browser should support CSS 3D transtorms 66 | (pfx('perspective') !== null) && 67 | 68 | // Browser should support `classList` and `dataset` APIs 69 | (dummy.classList) && 70 | (dummy.dataset) 71 | -------------------------------------------------------------------------------- /src/utils/initStepData.js: -------------------------------------------------------------------------------- 1 | import toNumber from './toNumber' 2 | import { transitionDuration } from './index' 3 | 4 | /* 默认旋转顺序 */ 5 | export const defaultRotateOrder = ['x', 'y', 'z'] 6 | 7 | /* when receive steps from vue props 8 | * init the steps data for computing 9 | * transform empty number to default 10 | * 初始化数据,将参数不足的部分补足 11 | * */ 12 | const initStepData = data => ({ 13 | translate: { 14 | x: toNumber(data.x), 15 | y: toNumber(data.y), 16 | z: toNumber(data.z), 17 | }, 18 | rotate: { 19 | x: toNumber(data.rotateX), 20 | y: toNumber(data.rotateY), 21 | z: toNumber(data.rotateZ || data.rotate), 22 | order: data.rotateOrder || defaultRotateOrder, 23 | }, 24 | scale: toNumber(data.scale, 1), 25 | transitionDuration: data.transitionDuration ? transitionDuration(data.transitionDuration) : null, 26 | transitionTimingFunction: data.transitionTimingFunction, 27 | }) 28 | 29 | export default initStepData 30 | -------------------------------------------------------------------------------- /src/utils/reverseData.js: -------------------------------------------------------------------------------- 1 | /* reverse transform 3d data 2 | * used for canvas part in viewport 3 | * @parm Object data, the data is generated from initStepData, 4 | * so every needed property exists surely 5 | * @return Object reversed data 6 | * 反转每部的偏移数据,给impress-canvas用 7 | **/ 8 | const reverseData = data => ({ 9 | translate: { 10 | x: -data.translate.x, 11 | y: -data.translate.y, 12 | z: -data.translate.z, 13 | }, 14 | rotate: { 15 | x: -data.rotate.x, 16 | y: -data.rotate.y, 17 | z: -data.rotate.z, 18 | order: [data.rotate.order[2], data.rotate.order[1], data.rotate.order[0]], 19 | }, 20 | scale: data.scale ? (1 / data.scale) : 1, 21 | }) 22 | 23 | export default reverseData 24 | -------------------------------------------------------------------------------- /src/utils/toNumber.js: -------------------------------------------------------------------------------- 1 | // `toNumber` takes a value given as `numeric` parameter and tries to turn 2 | // it into a number. If it is not possible it returns 0 (or other value 3 | // given as `fallback`). 4 | export default function toNumber(numeric, fallback) { 5 | numeric = Number(numeric) 6 | return Number.isNaN(numeric) ? (fallback || 0) : numeric 7 | } 8 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count; 11 | this.expected = count; 12 | this.pass = function (val) { 13 | return val === this.expected; 14 | } 15 | this.value = function (res) { 16 | return res.value; 17 | } 18 | this.command = function (cb) { 19 | var self = this; 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length; 22 | }, [selector], function (res) { 23 | cb.call(self, res); 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/guide#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar', 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing'; 3 | var server = require('../../build/dev-server.js'); 4 | 5 | // 2. run the nightwatch test suite against it 6 | // to run in additional browsers: 7 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 8 | // 2. add it to the --env flag below 9 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 10 | // For more information on Nightwatch's config file, see 11 | // http://nightwatchjs.org/guide#settings-file 12 | var opts = process.argv.slice(2); 13 | if (opts.indexOf('--config') === -1) { 14 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']); 15 | } 16 | if (opts.indexOf('--env') === -1) { 17 | opts = opts.concat(['--env', 'chrome']); 18 | } 19 | 20 | var spawn = require('cross-spawn'); 21 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }); 22 | 23 | runner.on('exit', function (code) { 24 | server.close(); 25 | process.exit(code); 26 | }); 27 | 28 | runner.on('error', function (err) { 29 | server.close(); 30 | throw err; 31 | }); 32 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function test(browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL; 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end(); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /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('es6-promise/auto') 6 | 7 | // require all test files (files that ends with .spec.js) 8 | const testsContext = require.context('./specs', true, /\.spec$/); 9 | testsContext.keys().forEach(testsContext); 10 | 11 | // require all src files except main.js for coverage. 12 | // you can also change this to match only the subset of files that 13 | // you want coverage for. 14 | const srcContext = require.context('src', true, /\.js$/); 15 | srcContext.keys().forEach(srcContext); 16 | -------------------------------------------------------------------------------- /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 | const path = require('path') 7 | const merge = require('webpack-merge') 8 | const baseConfig = require('../../build/webpack.base.conf') 9 | const utils = require('../../build/utils') 10 | const webpack = require('webpack') 11 | 12 | const projectRoot = path.resolve(__dirname, '../../') 13 | 14 | const webpackConfig = merge(baseConfig, { 15 | // use inline sourcemap for karma-sourcemap-loader 16 | module: { 17 | loaders: utils.styleLoaders(), 18 | }, 19 | devtool: '#inline-source-map', 20 | vue: { 21 | loaders: { 22 | js: 'isparta', 23 | }, 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin({ 27 | 'process.env': require('../../config/test.env'), 28 | }), 29 | ], 30 | }) 31 | 32 | // no need for app entry during tests 33 | delete webpackConfig.entry; 34 | 35 | // make sure isparta loader is applied before eslint 36 | webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || []; 37 | webpackConfig.module.preLoaders.unshift({ 38 | test: /\.js$/, 39 | loader: 'isparta', 40 | include: path.resolve(projectRoot, 'src'), 41 | }); 42 | 43 | // only apply babel for test files when using isparta 44 | webpackConfig.module.loaders.some((loader) => { 45 | if (loader.loader === 'babel') { 46 | loader.include = path.resolve(projectRoot, 'test/unit') 47 | return true 48 | } 49 | return false 50 | }) 51 | 52 | module.exports = function karmaConfig(config) { 53 | config.set({ 54 | // to run in additional browsers: 55 | // 1. install corresponding karma launcher 56 | // http://karma-runner.github.io/0.13/config/browsers.html 57 | // 2. add it to the `browsers` array below. 58 | browsers: ['PhantomJS'], 59 | // browsers: ['desktop'], 60 | customLaunchers: { 61 | mobile: { 62 | base: 'Chrome', 63 | flags: ['--window-size=320,600'], 64 | }, 65 | desktop: { 66 | base: 'Chrome', 67 | flags: ['--window-size=700,600'], 68 | }, 69 | }, 70 | frameworks: ['mocha', 'expect'], 71 | reporters: ['spec', 'coverage'], 72 | files: ['./index.js'], 73 | preprocessors: { 74 | './index.js': ['webpack', 'sourcemap'], 75 | }, 76 | webpack: webpackConfig, 77 | webpackMiddleware: { 78 | noInfo: true, 79 | }, 80 | coverageReporter: { 81 | dir: './coverage', 82 | reporters: [ 83 | { type: 'lcov', subdir: '.' }, 84 | { type: 'text-summary' }, 85 | ], 86 | }, 87 | singleRun: false, 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /test/unit/specs/components/viewport.spec.js: -------------------------------------------------------------------------------- 1 | import expect, { spyOn, createSpy } from 'expect' 2 | import Vue from 'vue' 3 | import lolex from 'lolex' 4 | import VueImpress from '../../../../src/index' 5 | import Fullscreen from '../../../../example/Fullscreen.vue' 6 | import { pfx, scale, rotate, translate, transitionDuration } from '../../../../src/utils' 7 | import reverseData from '../../../../src/utils/reverseData' 8 | import computeScale from '../../../../src/utils/computeScale' 9 | 10 | 11 | describe('vue-impress fullscreen mode', () => { 12 | Vue.use(VueImpress) 13 | 14 | let div 15 | let instance 16 | 17 | beforeEach(() => { 18 | div = document.createElement('div') 19 | const inner = document.createElement('div') 20 | div.appendChild(inner) 21 | document.body.appendChild(div) 22 | instance = new Vue(Fullscreen).$mount(inner) 23 | }) 24 | 25 | afterEach(() => { 26 | instance.$destroy() 27 | document.body.removeChild(div) 28 | }) 29 | 30 | 31 | it('first step( steps[0] ) is default active', (done) => { 32 | const steps = document.querySelectorAll('.impress-step') 33 | Vue.nextTick(() => { 34 | expect(steps[0].classList.contains('active')).toBe(true) 35 | done() 36 | }) 37 | }) 38 | 39 | it('viewport instance gotoStep method', (done) => { 40 | const viewport = instance.$refs.impress 41 | viewport.gotoStep(2) 42 | Vue.nextTick(() => { 43 | expect(typeof viewport.resize).toBe('function') 44 | const steps = document.querySelectorAll('.impress-step') 45 | expect(steps[2].classList.contains('active')).toBe(true) 46 | 47 | const viewportStyle = document.querySelector('.impress-viewport').style 48 | 49 | const duration = transitionDuration(instance.config.transitionDuration) 50 | expect(viewportStyle.transitionDuration).toBe(duration) 51 | 52 | /* in phantomjs is webkitPerspective 53 | * phantomjs中的还是webkitPerspective */ 54 | const viewportPerspective = viewportStyle[pfx('perspective')] 55 | const containerScale = viewport.containerScale 56 | const containerPerspective = viewport.config.perspective / containerScale 57 | expect(viewportPerspective).toBe(`${containerPerspective}px`) 58 | const currentData = viewport.stepsData[2] 59 | const target = reverseData(currentData) 60 | 61 | /* in phantomjs is webkitTransform 62 | * phantomjs中的还是webkitTransform */ 63 | const viewportTransform = viewportStyle[pfx('transform')] 64 | expect(viewportTransform).toBe(scale(target.scale * containerScale).trim()) 65 | 66 | const canvasStyle = document.querySelector('.impress-canvas').style 67 | const canvasTransitionDuration = canvasStyle[pfx('transitionDuration')] 68 | expect(canvasTransitionDuration).toBe(duration) 69 | 70 | const canvasTransform = canvasStyle[pfx('transform')] 71 | expect(canvasTransform).toBe((rotate(target.rotate, true) + 72 | translate(target.translate)).trim()) 73 | 74 | done() 75 | }) 76 | }) 77 | 78 | it('test events impress:goto', () => { 79 | const viewport = instance.$refs.impress 80 | spyOn(viewport, 'gotoStep') 81 | viewport.$emit('impress:goto', 1) 82 | expect(viewport.gotoStep).toHaveBeenCalledWith(1) 83 | }) 84 | 85 | it('test events impress:stepenter, impress:stepleave', (done) => { 86 | const clock = lolex.install() 87 | const viewport = instance.$refs.impress 88 | const stepEnterSpy = createSpy() 89 | const stepLeaveSpy = createSpy() 90 | viewport.$on('impress:stepenter', stepEnterSpy) 91 | viewport.$on('impress:stepleave', stepLeaveSpy) 92 | expect(stepEnterSpy).toNotHaveBeenCalled() 93 | expect(stepLeaveSpy).toNotHaveBeenCalled() 94 | viewport.gotoStep(2) 95 | expect(stepLeaveSpy).toHaveBeenCalledWith(0) 96 | setTimeout(() => { 97 | expect(stepEnterSpy).toHaveBeenCalledWith(2) 98 | clock.uninstall() 99 | done() 100 | }, instance.config.transitionDuration) 101 | clock.tick(instance.config.transitionDuration) 102 | }) 103 | 104 | it('click step to goto that step', () => { 105 | const viewport = instance.$refs.impress 106 | const spy = spyOn(viewport, 'gotoStep') 107 | const e = document.createEvent('HTMLEvents') 108 | e.initEvent('click', false, true) 109 | const steps = document.querySelectorAll('.impress-step') 110 | expect(spy).toNotHaveBeenCalled() 111 | steps[1].dispatchEvent(e) 112 | expect(spy).toHaveBeenCalled() 113 | }) 114 | 115 | it('nextStep', () => { 116 | const viewport = instance.$refs.impress 117 | expect(viewport.stepIndex).toBe(0) 118 | viewport.nextStep() 119 | expect(viewport.stepIndex).toBe(1) 120 | 121 | viewport.gotoStep(viewport.stepsData.length - 1) 122 | viewport.nextStep() 123 | expect(viewport.stepIndex).toBe(0) 124 | }) 125 | 126 | it('prevStep', () => { 127 | const viewport = instance.$refs.impress 128 | expect(viewport.stepIndex).toBe(0) 129 | viewport.prevStep() 130 | expect(viewport.stepIndex).toBe(viewport.stepsData.length - 1) 131 | 132 | viewport.prevStep() 133 | expect(viewport.stepIndex).toBe(viewport.stepsData.length - 2) 134 | }) 135 | 136 | it('when step active, wrapper should has this step id in classList', (done) => { 137 | const viewport = instance.$refs.impress 138 | expect(viewport.stepClass).toBe('firstStep') 139 | const viewportDom = document.querySelector('.impress-viewport') 140 | console.log(viewport.$data); 141 | Vue.nextTick(() => { 142 | expect(viewportDom.classList.contains(viewport.stepClass)).toBe(true) 143 | viewport.gotoStep(viewport.$data.stepsData.length - 1) 144 | Vue.nextTick(() => { 145 | expect(viewportDom.classList.contains('overview')).toBe(true) 146 | done() 147 | }) 148 | }) 149 | }) 150 | }) 151 | 152 | describe('vue-impress none fullscreen mode', () => { 153 | Vue.use(VueImpress) 154 | 155 | let div 156 | let instance 157 | 158 | beforeEach(() => { 159 | div = document.createElement('div') 160 | div.style.position = 'absolute' 161 | div.style.width = '800px' 162 | div.style.height = '400px' 163 | 164 | const inner = document.createElement('div') 165 | div.appendChild(inner) 166 | document.body.appendChild(div) 167 | const data = Fullscreen.data() 168 | data.config.fullscreen = false 169 | const NoneFullscreen = Object.assign({}, Fullscreen, { 170 | data() { 171 | return data 172 | }, 173 | }) 174 | instance = new Vue(NoneFullscreen).$mount(inner) 175 | }) 176 | 177 | afterEach(() => { 178 | instance.$destroy() 179 | document.body.removeChild(div) 180 | }) 181 | 182 | 183 | it('viewport will scale by the container size', (done) => { 184 | const viewport = instance.$refs.impress 185 | Vue.nextTick(() => { 186 | expect(typeof viewport.resize).toBe('undefined') 187 | const containerScale = computeScale({ 188 | width: div.clientWidth, 189 | height: div.clientHeight, 190 | }, viewport.config) 191 | expect(viewport.containerScale).toEqual(containerScale) 192 | done() 193 | }) 194 | }) 195 | }) 196 | 197 | describe('vue-impress slot insert element', () => { 198 | Vue.use(VueImpress) 199 | 200 | let div 201 | let instance 202 | 203 | beforeEach(() => { 204 | div = document.createElement('div') 205 | const inner = document.createElement('div') 206 | div.appendChild(inner) 207 | document.body.appendChild(div) 208 | instance = new Vue({ 209 | template: '', 210 | data() { 211 | return { 212 | config: { 213 | width: 100, 214 | height: 100 215 | }, 216 | steps: [{ 217 | x: 0, 218 | content: 'a', 219 | }] 220 | } 221 | } 222 | }).$mount(inner) 223 | }) 224 | 225 | afterEach(() => { 226 | instance.$destroy() 227 | document.body.removeChild(div) 228 | }) 229 | 230 | 231 | it('viewport insert element', (done) => { 232 | Vue.nextTick(() => { 233 | const viewport = document.querySelector('.impress-viewport') 234 | const a = viewport.querySelector('a') 235 | expect(a.classList.contains('a-element')).toBe(true) 236 | done() 237 | }) 238 | }) 239 | }) 240 | -------------------------------------------------------------------------------- /test/unit/specs/utils.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import { translate, rotate, scale, impressSupported, transitionDuration } from '../../../src/utils' 3 | 4 | describe('test utils', () => { 5 | it('translate', () => { 6 | const t = { 7 | x: 100, 8 | y: 200, 9 | z: 300, 10 | } 11 | expect(translate(t)).toEqual(` translate3d(${t.x}px, ${t.y}px, ${t.z}px)`) 12 | }) 13 | 14 | it('rotate', () => { 15 | const t = { 16 | x: 100, 17 | y: 200, 18 | z: 300, 19 | order: ['x', 'y', 'z'], 20 | } 21 | expect(rotate(t)).toEqual(' rotateX(100deg) rotateY(200deg) rotateZ(300deg)') 22 | t.order = ['y', 'x', 'z'] 23 | expect(rotate(t)).toEqual(' rotateY(200deg) rotateX(100deg) rotateZ(300deg)') 24 | t.order = ['z', 'y', 'x'] 25 | expect(rotate(t)).toEqual(' rotateZ(300deg) rotateY(200deg) rotateX(100deg)') 26 | }) 27 | 28 | it('scale', () => { 29 | const s = 6 30 | expect(scale(s)).toBe(` scale(${s})`) 31 | }) 32 | 33 | /* in test env, phantomjs or chrome or firefox test env, it is sure to be true */ 34 | it('impressSupported', () => { 35 | expect(impressSupported).toBe(true) 36 | }) 37 | 38 | it('transitionDuration', () => { 39 | expect(transitionDuration(2000)).toBe('2s') 40 | 41 | expect(transitionDuration('1.4s', true)).toBe(1400) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/unit/specs/utils/computeScale.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import computeScale from '../../../../src/utils/computeScale' 3 | 4 | describe('utils/computeScale', () => { 5 | it('computeScale use window as container', () => { 6 | const container = { 7 | width: 200, 8 | height: 500, 9 | } 10 | const config = { 11 | width: 400, 12 | height: 800, 13 | } 14 | 15 | const scale = computeScale(container, config) 16 | expect(scale).toBe(container.width / config.width) 17 | 18 | const container1 = { 19 | width: 300, 20 | height: 500, 21 | } 22 | const config1 = { 23 | width: 400, 24 | height: 800, 25 | } 26 | 27 | const scale1 = computeScale(container1, config1) 28 | expect(scale1).toBe(container1.height / config1.height) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /test/unit/specs/utils/debounce.spec.js: -------------------------------------------------------------------------------- 1 | import expect, { createSpy } from 'expect' 2 | import debounce from '../../../../src/utils/debounce' 3 | 4 | describe('utils', () => { 5 | it('debounce function with param', (done) => { 6 | const spy = createSpy() 7 | const fn = debounce(spy, 10) 8 | 9 | fn(123) 10 | expect(spy).toNotHaveBeenCalled() 11 | 12 | setTimeout(() => { 13 | expect(spy).toHaveBeenCalledWith(123) 14 | done() 15 | }, 11) 16 | }) 17 | 18 | it('debounce many times', (done) => { 19 | const spy = createSpy() 20 | const fn = debounce(spy, 10) 21 | 22 | fn() 23 | expect(spy).toNotHaveBeenCalled() 24 | 25 | setTimeout(() => { 26 | expect(spy).toNotHaveBeenCalled() 27 | setTimeout(() => { 28 | expect(spy).toHaveBeenCalled() 29 | done() 30 | }, 10) 31 | }, 9) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/unit/specs/utils/initData.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import initStepData, { defaultRotateOrder } from '../../../../src/utils/initStepData' 3 | import reverseData from '../../../../src/utils/reverseData' 4 | 5 | describe('test utils', () => { 6 | it('initStepData and reverseData', () => { 7 | const data = { 8 | x: 10, 9 | y: 20, 10 | rotateX: 30, 11 | rotateY: 40, 12 | transitionTimingFunction: 'linear', 13 | } 14 | const stepData = initStepData(data) 15 | expect(stepData.translate).toEqual({ 16 | x: data.x, 17 | y: data.y, 18 | z: 0, 19 | }) 20 | 21 | expect(stepData.rotate).toEqual({ 22 | x: data.rotateX, 23 | y: data.rotateY, 24 | z: 0, 25 | order: defaultRotateOrder, 26 | }) 27 | expect(stepData.scale).toBe(1) 28 | expect(stepData.transitionDuration).toBe(null) 29 | expect(stepData.transitionTimingFunction).toBe('linear') 30 | 31 | const order = Object.assign([], defaultRotateOrder) 32 | expect(reverseData(stepData)).toEqual({ 33 | translate: { 34 | x: -10, 35 | y: -20, 36 | z: 0, 37 | }, 38 | rotate: { 39 | x: -30, 40 | y: -40, 41 | z: 0, 42 | order: order.reverse(), 43 | }, 44 | scale: 1, 45 | }) 46 | 47 | const data1 = { 48 | z: 30, 49 | rotate: 30, 50 | scale: 0, 51 | transitionDuration: 3000, 52 | rotateOrder: ['y', 'z', 'x'] 53 | } 54 | const stepData1 = initStepData(data1) 55 | expect(stepData1.translate).toEqual({ 56 | x: 0, 57 | y: 0, 58 | z: 30, 59 | }) 60 | 61 | expect(stepData1.rotate).toEqual({ 62 | x: 0, 63 | y: 0, 64 | z: 30, 65 | order: ['y', 'z', 'x'], 66 | }) 67 | expect(stepData1.scale).toEqual(data1.scale) 68 | expect(stepData1.transitionDuration).toBe('3s') 69 | expect(reverseData(stepData1)).toEqual({ 70 | translate: { 71 | x: 0, 72 | y: 0, 73 | z: -30, 74 | }, 75 | rotate: { 76 | x: 0, 77 | y: 0, 78 | z: -30, 79 | order: ['x', 'z', 'y'], 80 | }, 81 | scale: 1, 82 | }) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /test/unit/specs/utils/toNumber.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import toNumber from '../../../../src/utils/toNumber' 3 | 4 | describe('test utils', () => { 5 | it('toNumber', () => { 6 | let n = toNumber('100') 7 | expect(n).toBe(100) 8 | 9 | n = toNumber('a', 10) 10 | expect(n).toBe(10) 11 | 12 | n = toNumber('a') 13 | expect(n).toBe(0) 14 | 15 | n = toNumber('a') 16 | expect(n).toBe(0) 17 | 18 | n = toNumber(Number.NaN, 11) 19 | expect(n).toBe(11) 20 | 21 | n = toNumber(12, 12) 22 | expect(n).toBe(12) 23 | }) 24 | }) 25 | --------------------------------------------------------------------------------