├── .babelrc ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── LICENCE ├── README.md ├── build ├── build-lib.js ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.lib.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── dist └── lib │ ├── vue-booklet.min.css │ ├── vue-booklet.min.css.map │ ├── vue-booklet.min.js │ └── vue-booklet.min.js.map ├── index-template.html ├── index.html ├── package.json ├── src ├── app.vue ├── assets │ ├── arrow_left.png │ ├── arrow_right.png │ └── black_cat_black_and_white-300px.png ├── components │ └── book.vue ├── docs.js ├── lib.js └── styles │ ├── app.scss │ └── lib.scss ├── static └── .gitkeep └── yarn.lock /.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-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.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 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | src/styles/*.css 7 | index.html 8 | dist/docs 9 | 10 | # Editor directories and files 11 | .idea 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Mr.Twister 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-booklet 2 | *Vue book-like compoment for create a your own e-book* 3 | 4 | Vue Booklet is a compoment which can let your create a flipable book fastly, and is responsive. 5 | As it's used css animation, it will not support IE 9 and other old browser. 6 | 7 | ## Demo 8 | 9 | http://book.sardo.work 10 | 11 | ## Installation 12 | ```bash 13 | npm install vue-booklet --save 14 | ``` 15 | 16 | or 17 | 18 | ```bash 19 | yarn add vue-booklet 20 | ``` 21 | 22 | ## Usage 23 | 24 | Import module from vue-booklet, then register it to Vue: 25 | 26 | ```js 27 | import booklet from 'vue-booklet'; 28 | 29 | new Vue({ 30 | el: '#your-dom-element', 31 | components: { 32 | booklet 33 | } 34 | }) 35 | ``` 36 | 37 | And import the css file in your html file: 38 | ```html 39 | 40 | ``` 41 | 42 | After that, you can use vue-booklet on your project now. 43 | 44 | ### Without webpack 45 | 46 | ```html 47 | 48 | 49 | 50 |
51 | 52 |
53 |
54 |

My first book

55 |
56 |
57 |
58 |
59 |

Hello World !

60 |

Using vue-booklet to create a book which can fliped.

61 |

Please feel free to use it.

62 |
63 |
64 |
65 |
66 | 67 | 68 | 69 | 79 | ``` 80 | 81 | ## Example 82 | ```html 83 | 84 |
85 |
86 |

My first book

87 |
88 |
89 |
90 |
91 |

Hello World !

92 |

Using vue-booklet to create a book which can fliped.

93 |

Please feel free to use it.

94 |
95 |
96 |
97 | ``` 98 | If you want to add page to book programmatically, here are one more example: 99 | 100 | ```js 101 | const newPage = document.createElement('div'); 102 | const newContent = document.createElement('div'); 103 | newPage.className = 'page'; 104 | newContent.className= 'content'; 105 | newPage.appendChild(newContent); 106 | pages.appendChild(newPage); 107 | ``` 108 | ## Methods 109 | 110 | #### prevPage(), nextPage() 111 | Function for control the book without button. 112 | 113 | ```js 114 | this.$refs.book.nextPage(); 115 | this.$refs.book.prevPage(); 116 | ``` 117 | 118 | #### movePage(pageNum) 119 | Function for allow user move to specify page. 120 | 121 | ```js 122 | const pageNumber = '1'; 123 | this.$refs.book.movePage(pageNumber); 124 | ``` 125 | 126 | ## Props 127 | 128 | #### displayPageNumber(optional) 129 | Default value is true. Will not show page number when it is false. 130 | 131 | #### enableControl(optional) 132 | Default value is true. Will not allow user control the book when it is false. 133 | If you want to control it, add ref to book compoment and call function like this: 134 | ```js 135 | this.$refs.book.nextPage(); 136 | this.$refs.book.prevPage(); 137 | ``` 138 | 139 | 140 | #### displayButton(optional) 141 | Default value is true. Will not show nextPage and PrevPage button when it is false. 142 | Only work when the value of allowFlip is true. 143 | 144 | #### enableSelectPage(optional) 145 | Default value is true. Will not allow user select page when it is false. 146 | Only work when the value of allowFlip is true. 147 | 148 | #### langcode(optional) 149 | Set the language of booklet UI. 150 | Current support language is English (en), Traditional Chinese (zh-hant) and Simplified Chinese (zh-hans). You can add new langcode with translation props. 151 | Default value is en. 152 | 153 | #### translation(optional) 154 | Define translation of booklet UI. You can pass object or JSON string with translated text to it. 155 | Default value: 156 | ```js 157 | { 158 | 'en': { 159 | 'selectPage': 'Select page', 160 | 'pages': 'Pages', 161 | 'prev': 'Prev', 162 | 'next': 'Next', 163 | }, 164 | 'zh-hant': { 165 | 'selectPage': '跳至指定頁數', 166 | 'pages': '頁數', 167 | 'prev': '上一頁', 168 | 'next': '下一頁', 169 | }, 170 | 'zh-hans': { 171 | 'selectPage': '跳至指定页数', 172 | 'pages': '页数', 173 | 'prev': '上一页', 174 | 'next': '下一页', 175 | }, 176 | } 177 | ``` 178 | 179 | #### pageTransitionTime (optional) 180 | Set the transition time of each book page. 181 | 182 | Default value is 0.8s. 183 | 184 | #### onOpened(book, position) (optional) 185 | A callback which happens after book opened. Pass the book dom which you can control it, and position of book. 186 | 187 | #### onClosed(book, position) (optional) 188 | A callback which happens after book closed. Pass the book dom which you can control it, and position of book. 189 | 190 | #### onFlipStart(currentPage, direction) (optional) 191 | A callback which happens before a page filped. Pass the page dom which you can control it, 192 | and direction for you to know filped to next page or previous page. 193 | 194 | #### onFlipEnd(currentPage, direction) (optional) 195 | A callback which happens after a page filped. Pass the page dom which you can control it, 196 | and direction for you to know filped to next page or previous page. 197 | 198 | 199 | ## Developing 200 | 201 | ``` bash 202 | # install dependencies 203 | npm install 204 | 205 | # serve with hot reload at localhost:8080 206 | npm run dev 207 | 208 | # build for production with minification 209 | npm run build 210 | 211 | # build for production and view the bundle analyzer report 212 | npm run build --report 213 | ``` 214 | -------------------------------------------------------------------------------- /build/build-lib.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.lib.conf') 12 | 13 | var spinner = ora('building for library...') 14 | spinner.start() 15 | 16 | rm(path.join(config.lib.assetsRoot, config.lib.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: Now you are ready to publish your library to npm.\n' + 32 | ' Then users can import it as an es6 module: import vueMybook from \'vue-booklet\'\n' 33 | )) 34 | }) 35 | }) -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').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 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /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 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = require('./webpack.dev.conf') 14 | 15 | // default port where dev server listens for incoming traffic 16 | var port = process.env.PORT || config.dev.port 17 | // automatically open browser, if not set will be false 18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 19 | // Define HTTP proxies to your custom API backend 20 | // https://github.com/chimurai/http-proxy-middleware 21 | var proxyTable = config.dev.proxyTable 22 | 23 | var app = express() 24 | var compiler = webpack(webpackConfig) 25 | 26 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 27 | publicPath: webpackConfig.output.publicPath, 28 | quiet: true 29 | }) 30 | 31 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 32 | log: () => {}, 33 | heartbeat: 2000 34 | }) 35 | // force page reload when html-webpack-plugin template changes 36 | compiler.plugin('compilation', function (compilation) { 37 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 38 | hotMiddleware.publish({ action: 'reload' }) 39 | cb() 40 | }) 41 | }) 42 | 43 | // proxy api requests 44 | Object.keys(proxyTable).forEach(function (context) { 45 | var options = proxyTable[context] 46 | if (typeof options === 'string') { 47 | options = { target: options } 48 | } 49 | app.use(proxyMiddleware(options.filter || context, options)) 50 | }) 51 | 52 | // handle fallback for HTML5 history API 53 | app.use(require('connect-history-api-fallback')()) 54 | 55 | // serve webpack bundle output 56 | app.use(devMiddleware) 57 | 58 | // enable hot-reload and state-preserving 59 | // compilation error display 60 | app.use(hotMiddleware) 61 | 62 | // serve pure static assets 63 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 64 | app.use(staticPath, express.static('./static')) 65 | 66 | var uri = 'http://localhost:' + port 67 | 68 | var _resolve 69 | var readyPromise = new Promise(resolve => { 70 | _resolve = resolve 71 | }) 72 | 73 | console.log('> Starting dev server...') 74 | devMiddleware.waitUntilValid(() => { 75 | console.log('> Listening at ' + uri + '\n') 76 | // when env is testing, don't need open it 77 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 78 | opn(uri) 79 | } 80 | _resolve() 81 | }) 82 | 83 | var server = app.listen(port) 84 | 85 | module.exports = { 86 | ready: readyPromise, 87 | close: () => { 88 | server.close() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /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.assetsLibPath = function (_path) { 13 | return path.posix.join(config.lib.assetsSubDirectory, _path) 14 | } 15 | 16 | exports.cssLoaders = function (options) { 17 | options = options || {} 18 | 19 | var cssLoader = { 20 | loader: 'css-loader', 21 | options: { 22 | minimize: process.env.NODE_ENV === 'production', 23 | sourceMap: options.sourceMap 24 | } 25 | } 26 | 27 | // generate loader string to be used with extract text plugin 28 | function generateLoaders (loader, loaderOptions) { 29 | var loaders = [cssLoader] 30 | if (loader) { 31 | loaders.push({ 32 | loader: loader + '-loader', 33 | options: Object.assign({}, loaderOptions, { 34 | sourceMap: options.sourceMap 35 | }) 36 | }) 37 | } 38 | 39 | // Extract CSS when that option is specified 40 | // (which is the case during production build) 41 | if (options.extract) { 42 | return ExtractTextPlugin.extract({ 43 | use: loaders, 44 | fallback: 'vue-style-loader' 45 | }) 46 | } else { 47 | return ['vue-style-loader'].concat(loaders) 48 | } 49 | } 50 | 51 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 52 | return { 53 | css: generateLoaders(), 54 | postcss: generateLoaders(), 55 | less: generateLoaders('less'), 56 | sass: generateLoaders('sass', { indentedSyntax: true }), 57 | scss: generateLoaders('sass'), 58 | stylus: generateLoaders('stylus'), 59 | styl: generateLoaders('stylus') 60 | } 61 | } 62 | 63 | // Generate loaders for standalone style files (outside of .vue) 64 | exports.styleLoaders = function (options) { 65 | var output = [] 66 | var loaders = exports.cssLoaders(options) 67 | for (var extension in loaders) { 68 | var loader = loaders[extension] 69 | output.push({ 70 | test: new RegExp('\\.' + extension + '$'), 71 | use: loader 72 | }) 73 | } 74 | return output 75 | } 76 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }), 12 | transformToRequire: { 13 | video: 'src', 14 | source: 'src', 15 | img: 'src', 16 | image: 'xlink:href' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | docs: './src/docs.js', 13 | ['vue-booklet']: './src/lib.js' 14 | }, 15 | output: { 16 | path: config.build.assetsRoot, 17 | filename: '[name].js', 18 | publicPath: process.env.NODE_ENV === 'production' 19 | ? config.build.assetsPublicPath 20 | : config.dev.assetsPublicPath 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.vue', '.json'], 24 | alias: { 25 | 'vue$': 'vue/dist/vue.esm.js', 26 | '@': resolve('src') 27 | } 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.vue$/, 33 | loader: 'vue-loader', 34 | options: vueLoaderConfig 35 | }, 36 | { 37 | test: /\.js$/, 38 | loader: 'babel-loader', 39 | include: [resolve('src'), resolve('test')] 40 | }, 41 | { 42 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 43 | loader: 'url-loader', 44 | options: { 45 | limit: 10000, 46 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 47 | } 48 | }, 49 | { 50 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 51 | loader: 'url-loader', 52 | options: { 53 | limit: 10000, 54 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 55 | } 56 | }, 57 | { 58 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index-template.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /build/webpack.lib.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 8 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 9 | 10 | var env = config.lib.env 11 | 12 | baseWebpackConfig.entry = { 13 | 'vue-booklet': './src/lib.js' 14 | } 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.lib.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.lib.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.lib.assetsRoot, 26 | filename: utils.assetsLibPath('[name].min.js'), 27 | library: '[name]', 28 | libraryTarget: 'umd' 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new webpack.optimize.UglifyJsPlugin({ 36 | compress: { 37 | warnings: false 38 | }, 39 | sourceMap: true 40 | }), 41 | // extract css into its own file 42 | new ExtractTextPlugin({ 43 | filename: utils.assetsLibPath('[name].min.css') 44 | }), 45 | // Compress extracted CSS. We are using this plugin so that possible 46 | // duplicated CSS from different components can be deduped. 47 | new OptimizeCSSPlugin({ 48 | cssProcessorOptions: { 49 | safe: true 50 | } 51 | }) 52 | ] 53 | }) 54 | 55 | if (config.lib.productionGzip) { 56 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 57 | 58 | webpackConfig.plugins.push( 59 | new CompressionWebpackPlugin({ 60 | asset: '[path].gz[query]', 61 | algorithm: 'gzip', 62 | test: new RegExp( 63 | '\\.(' + 64 | config.lib.productionGzipExtensions.join('|') + 65 | ')$' 66 | ), 67 | threshold: 10240, 68 | minRatio: 0.8 69 | }) 70 | ) 71 | } 72 | 73 | if (config.lib.bundleAnalyzerReport) { 74 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 75 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 76 | } 77 | 78 | module.exports = webpackConfig 79 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = config.build.env 13 | 14 | var webpackConfig = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ 17 | sourceMap: config.build.productionSourceMap, 18 | extract: true 19 | }) 20 | }, 21 | devtool: config.build.productionSourceMap ? '#source-map' : false, 22 | output: { 23 | path: config.build.assetsRoot, 24 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 25 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 26 | }, 27 | plugins: [ 28 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 29 | new webpack.DefinePlugin({ 30 | 'process.env': env 31 | }), 32 | new webpack.optimize.UglifyJsPlugin({ 33 | compress: { 34 | warnings: false 35 | }, 36 | sourceMap: true 37 | }), 38 | // extract css into its own file 39 | new ExtractTextPlugin({ 40 | filename: utils.assetsPath('css/[name].[contenthash].css') 41 | }), 42 | // Compress extracted CSS. We are using this plugin so that possible 43 | // duplicated CSS from different components can be deduped. 44 | new OptimizeCSSPlugin({ 45 | cssProcessorOptions: { 46 | safe: true 47 | } 48 | }), 49 | // generate dist index.html with correct asset hash for caching. 50 | // you can customize output by editing /index.html 51 | // see https://github.com/ampedandwired/html-webpack-plugin 52 | new HtmlWebpackPlugin({ 53 | filename: config.build.index, 54 | template: 'index-template.html', 55 | inject: true, 56 | minify: { 57 | removeComments: true, 58 | collapseWhitespace: true, 59 | removeAttributeQuotes: true 60 | // more options: 61 | // https://github.com/kangax/html-minifier#options-quick-reference 62 | }, 63 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 64 | chunksSortMode: 'dependency' 65 | }), 66 | // split your library css/js into separate files 67 | new webpack.optimize.CommonsChunkPlugin({ 68 | name: 'vue-booklet' 69 | }), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module, count) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /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 | index: path.resolve(__dirname, '../index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'docs', 10 | assetsPublicPath: '/dist', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | lib: { 25 | env: require('./prod.env'), 26 | assetsRoot: path.resolve(__dirname, '../dist'), 27 | assetsSubDirectory: 'lib', 28 | assetsPublicPath: '/', 29 | productionSourceMap: true, 30 | productionGzip: false, 31 | productionGzipExtensions: ['js', 'css'], 32 | bundleAnalyzerReport: process.env.npm_config_report 33 | }, 34 | dev: { 35 | env: require('./dev.env'), 36 | port: 8000, 37 | autoOpenBrowser: true, 38 | assetsSubDirectory: 'docs', 39 | assetsPublicPath: '/', 40 | proxyTable: {}, 41 | // CSS Sourcemaps off by default because relative paths are "buggy" 42 | // with this option, according to the CSS-Loader README 43 | // (https://github.com/webpack/css-loader#sourcemaps) 44 | // In our experience, they generally work as expected, 45 | // just be aware of this issue when enabling this option. 46 | cssSourceMap: false 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /dist/lib/vue-booklet.min.css: -------------------------------------------------------------------------------- 1 | .cover{background-color:#bca98a}.book-control-buttons,.control-page{display:none}.book .page .content{border:1px solid #ddd;padding:5%;margin-bottom:.5em;text-align:left}.select-page-wrapper{display:none;margin-top:1em;font-size:1.2em;text-align:center}.select-page-wrapper select{margin-left:1em;width:20%}.select-page-wrapper-mobile{display:block;text-align:center;font-size:1.2em;margin-top:1.2em;margin-bottom:1em}.select-page-wrapper-mobile select{margin-left:1em;width:20%}.page-number{display:none;margin-top:1em;margin-bottom:1em;font-size:1.2em;text-align:center}@media (min-width:768px){.vue-booklet{height:100vh;max-height:650px;margin:.5em}.book,.vue-booklet{width:100%;transform-style:preserve-3d}.book{font-family:Book Antiqua,Palatino,Palatino Linotype,Palatino LT STD,Georgia,serif;position:relative;height:100%;transition:all .8s}.control-page{position:absolute;display:flex;align-items:center;justify-content:center;width:100px;height:100%;z-index:99;transition:all .3s;opacity:0;background-image:linear-gradient(90deg,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001));background-repeat:repeat-x}.control-page:hover{opacity:.5}.control-page-left{left:0}.control-page-right{right:0}.book-control-buttons{display:block;text-align:center}.book-control-button{width:75px;height:40px;border:0;border-radius:2px;color:#fff;background-color:#72b890;font-size:16px;transition:all .3s;margin-top:1em;margin-bottom:1em;margin-right:1em}.book-control-button:focus,.book-control-button:hover{color:#fff;background-color:#759c86;border-color:#72b890}.closed{transform:translateX(-25%)}.closed-back{transform:translateX(25%)}.opened{transform:translateX(-1vw)}.page-number,.select-page-wrapper{display:block}.select-page-wrapper-mobile{display:none}.page{width:50%;color:#222;background-color:#fff;top:0;right:0;transform-origin:0 50%;border:1px solid #ddd;background:linear-gradient(90deg,#d9d9d9 0,#f9f9f9 3%,#fff 8%,#fff)}.page,.page .content{position:absolute;height:100%;transform-style:preserve-3d}.page .content{width:100%;max-width:100%;padding:5%;border:none;transform-origin:center center;backface-visibility:hidden;overflow:auto}.page .content:first-child{transform:translateZ(1px)}.page .content:nth-child(2){transform:rotateY(180deg) translateZ(1px)}.pages{position:relative;height:100%}.cover{background:none;background-color:#bca98a}.fliped{transform:rotateY(-180deg) translateZ(1px)}.progress{width:100%;height:20px;background-color:#ccc;overflow:hidden;cursor:pointer}.left{height:100%;width:30%;background-color:#adff2f;position:relative}.ball{height:100%;width:20px;border-radius:10px;background-color:red;position:absolute;right:-10px}}.back{background:none;background-color:#bca98a} -------------------------------------------------------------------------------- /dist/lib/vue-booklet.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/styles/src/styles/lib.scss"],"names":[],"mappings":"AAAA;EACE,0BAAyB,EAC1B;;AAED;EACE,cAAa,EACd;;AAED;EACE,cAAa,EACd;;AAED;EAGM,uBAAsB;EACtB,YAAW;EACX,qBAAoB;EACpB,iBAAgB,EACjB;;AAIL;EACE,cAAa;EACb,gBAAe;EACf,iBAAgB;EAChB,mBAAkB,EAMnB;EAVD;IAOI,iBAAgB;IAChB,WAAU,EACX;;AAGH;EACE,eAAc;EACd,mBAAkB;EAClB,iBAAgB;EAChB,kBAAiB;EACjB,mBAAkB,EAMnB;EAXD;IAQI,iBAAgB;IAChB,WAAU,EACX;;AAGH;EACE,cAAa;EACb,gBAAe;EACf,mBAAkB;EAClB,iBAAgB;EAChB,mBAAkB,EACnB;;AAED;EACE;IACE,YAAW;IACX,cAAa;IACb,kBAAiB;IACjB,cAAa;IACb,qCAAoC;IACpC,kCAAiC;IACjC,6BAA4B,EAC7B;EAED;IACE,wFAAuF;IACvF,mBAAkB;IAClB,YAAW;IACX,aAAY;IACZ,qCAAoC;IACpC,kCAAiC;IACjC,6BAA4B;IAC5B,oBAAmB,EACpB;EAED;IACE,mBAAkB;IAClB,cAAa;IACb,oBAAkB;IAClB,wBAAuB;IACvB,aAAY;IACZ,aAAY;IACZ,YAAW;IACX,oBAAmB;IACnB,WAAU;IACV,mGAAkG;IAClG,8FAA6F;IAC7F,+FAA8F;IAC9F,4BAA2B,EAK5B;IAlBD;MAgBI,aAAY,EACb;EAGH;IACE,QAAO,EACR;EAED;IACE,SAAQ,EACT;EAED;IACE,eAAc;IACd,mBAAkB,EACnB;EAED;IACE,YAAW;IACX,aAAY;IACZ,UAAS;IACT,mBAAkB;IAClB,eAAc;IACd,0BAAyB;IACzB,gBAAe;IACf,qBAAoB;IACpB,gBAAe;IACf,mBAAkB;IAClB,kBAAiB,EAOlB;IAlBD;MAcI,eAAc;MACd,0BAAyB;MACzB,sBAAqB,EACtB;EAGH;IACE,oCAAmC;IACnC,4BAA2B,EAC5B;EAED;IACE,mCAAkC;IAClC,2BAA0B,EAC3B;EAED;IACE,oCAAmC;IACnC,4BAA2B,EAC5B;EAED;IACE,eAAc,EACf;EAED;IACE,eAAc,EACf;EAED;IACE,cAAa,EACd;EAED;IACE,mBAAkB;IAClB,WAAU;IACV,aAAY;IACZ,YAAW;IACX,wBAAuB;IACvB,OAAM;IACN,SAAQ;IACR,wBAAuB;IACvB,uBAAsB;IACtB,wFAAoF;IACpF,qCAAoC;IACpC,kCAAiC;IACjC,6BAA4B,EA4B7B;IAzCD;MAgBI,mBAAkB;MAClB,YAAW;MACX,gBAAe;MACf,aAAY;MACZ,YAAW;MACX,aAAY;MACZ,gCAA+B;MAC/B,4BAA2B;MAC3B,eAAc;MACd,qCAAoC;MACpC,kCAAiC;MACjC,6BAA4B,EAa7B;MAxCH;QA8BM,mCAAkC;QAClC,gCAA+B;QAC/B,2BAA0B,EAC3B;MAjCL;QAoCM,mDAAkD;QAClD,gDAA+C;QAC/C,2CAA0C,EAC3C;EAIL;IACE,mBAAkB;IAClB,aAAY,EACb;EAED;IACE,iBAAgB;IAChB,0BAAyB,EAC1B;EAED;IACE,oDAAmD;IACnD,4CAA2C,EAC5C;EAED;IACE,YAAU;IACV,aAAW;IACX,uBAAsB;IACtB,iBAAgB;IAChB,gBAAe,EAChB;EACD;IACE,aAAW;IACX,WAAU;IACV,8BAA6B;IAC7B,mBAAkB,EACnB;EACD;IACE,aAAW;IACX,YAAU;IACV,4BAA0B;IAC1B,yBAAuB;IACvB,oBAAkB;IAClB,sBAAqB;IACrB,mBAAkB;IAClB,aAAY,EACb;;AAGH;EACE,iBAAgB;EAChB,0BAAyB,EAC1B","file":"lib/vue-booklet.min.css","sourcesContent":[".cover{\n background-color: #BCA98A;\n}\n\n.book-control-buttons{\n display: none;\n}\n\n.control-page{\n display: none;\n}\n\n.book{\n .page{\n .content{\n border: 1px solid #ddd;\n padding: 5%;\n margin-bottom: 0.5em;\n text-align: left;\n }\n }\n}\n\n.select-page-wrapper{\n display: none;\n margin-top: 1em;\n font-size: 1.2em;\n text-align: center;\n\n select{\n margin-left: 1em;\n width: 20%;\n }\n}\n\n.select-page-wrapper-mobile{\n display: block;\n text-align: center;\n font-size: 1.2em;\n margin-top: 1.2em;\n margin-bottom: 1em;\n\n select{\n margin-left: 1em;\n width: 20%;\n }\n}\n\n.page-number{\n display: none;\n margin-top: 1em;\n margin-bottom: 1em;\n font-size: 1.2em;\n text-align: center;\n}\n\n@media (min-width: 768px) {\n .vue-booklet{\n width: 100%;\n height: 100vh;\n max-height: 650px;\n margin: 0.5em;\n -webkit-transform-style: preserve-3d;\n -moz-transform-style: preserve-3d;\n transform-style: preserve-3d;\n }\n\n .book{\n font-family: Book Antiqua, Palatino, Palatino Linotype, Palatino LT STD, Georgia, serif;\n position: relative;\n width: 100%;\n height: 100%;\n -webkit-transform-style: preserve-3d;\n -moz-transform-style: preserve-3d;\n transform-style: preserve-3d;\n transition: all .8s;\n }\n\n .control-page{\n position: absolute;\n display: flex;\n align-items:center;\n justify-content: center;\n width: 100px;\n height: 100%;\n z-index: 99;\n transition: all .3s;\n opacity: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n\n &:hover{\n opacity: 0.5;\n }\n }\n\n .control-page-left{\n left: 0;\n }\n\n .control-page-right{\n right: 0;\n }\n\n .book-control-buttons{\n display: block;\n text-align: center;\n }\n\n .book-control-button{\n width: 75px;\n height: 40px;\n border: 0;\n border-radius: 2px;\n color: #ffffff;\n background-color: #72B890;\n font-size: 16px;\n transition: all 0.3s;\n margin-top: 1em;\n margin-bottom: 1em;\n margin-right: 1em;\n\n &:hover, &:focus{\n color: #ffffff;\n background-color: #759C86;\n border-color: #72B890;\n }\n }\n\n .closed{\n -webkit-transform: translateX(-25%);\n transform: translateX(-25%);\n }\n\n .closed-back{\n -webkit-transform: translateX(25%);\n transform: translateX(25%);\n }\n\n .opened{\n -webkit-transform: translateX(-1vw);\n transform: translateX(-1vw);\n }\n\n .page-number{\n display: block;\n }\n\n .select-page-wrapper{\n display: block;\n }\n\n .select-page-wrapper-mobile{\n display: none;\n }\n\n .page{\n position: absolute;\n width: 50%;\n height: 100%;\n color: #222;\n background-color: white;\n top: 0;\n right: 0;\n transform-origin: 0 50%;\n border: 1px solid #ddd;\n background: linear-gradient(to right, #d9d9d9 0%,#f9f9f9 3%,#ffffff 8%,#ffffff 100%);\n -webkit-transform-style: preserve-3d;\n -moz-transform-style: preserve-3d;\n transform-style: preserve-3d;\n\n .content{\n position: absolute;\n width: 100%;\n max-width: 100%;\n height: 100%;\n padding: 5%;\n border: none;\n transform-origin: center center;\n backface-visibility: hidden;\n overflow: auto;\n -webkit-transform-style: preserve-3d;\n -moz-transform-style: preserve-3d;\n transform-style: preserve-3d;\n\n &:nth-child(1){\n -webkit-transform: translateZ(1px);\n -moz-transform: translateZ(1px);\n transform: translateZ(1px);\n }\n\n &:nth-child(2){\n -webkit-transform: rotateY(180deg) translateZ(1px);\n -moz-transform: rotateY(180deg) translateZ(1px);\n transform: rotateY(180deg) translateZ(1px);\n }\n }\n }\n\n .pages{\n position: relative;\n height: 100%;\n }\n\n .cover{\n background: none;\n background-color: #BCA98A;\n }\n\n .fliped{\n -webkit-transform: rotateY(-180deg) translateZ(1px);\n transform: rotateY(-180deg) translateZ(1px);\n }\n\n .progress{\n width:100%;\n height:20px;\n background-color: #ccc;\n overflow: hidden;\n cursor: pointer;\n }\n .left{\n height:100%;\n width: 30%;\n background-color: greenyellow;\n position: relative;\n }\n .ball{\n height:100%;\n width:20px;\n -webkit-border-radius:10px;\n -moz-border-radius:10px;\n border-radius:10px;\n background-color: red;\n position: absolute;\n right: -10px;\n }\n}\n\n.back{\n background: none;\n background-color: #BCA98A;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/styles/src/styles/lib.scss"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/lib/vue-booklet.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["vue-booklet"]=t():e["vue-booklet"]=t()}(this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/",t(t.s=30)}([function(e,t,n){var o=n(22)("wks"),r=n(26),i=n(2).Symbol,s="function"==typeof i;(e.exports=function(e){return o[e]||(o[e]=s&&i[e]||(s?i:r)("Symbol."+e))}).store=o},function(e,t){var n=e.exports={version:"2.6.12"};"number"==typeof __e&&(__e=n)},function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(e,t,n){var o=n(9);e.exports=function(e){if(!o(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){e.exports=!n(19)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var o=n(7),r=n(11);e.exports=n(4)?function(e,t,n){return o.f(e,t,r(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var o=n(3),r=n(40),i=n(55),s=Object.defineProperty;t.f=n(4)?Object.defineProperty:function(e,t,n){if(o(e),t=i(t,!0),o(n),r)try{return s(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t){e.exports={}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,n){var o=n(22)("keys"),r=n(26);e.exports=function(e){return o[e]||(o[e]=r(e))}},function(e,t){var n=Math.ceil,o=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?o:n)(e)}},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){var o=n(35);e.exports=function(e,t,n){if(o(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,o){return e.call(t,n,o)};case 3:return function(n,o,r){return e.call(t,n,o,r)}}return function(){return e.apply(t,arguments)}}},function(e,t,n){var o=n(9),r=n(2).document,i=o(r)&&o(r.createElement);e.exports=function(e){return i?r.createElement(e):{}}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){var o=n(2),r=n(1),i=n(15),s=n(6),a=n(5),c=function(e,t,n){var l,u,f,p=e&c.F,d=e&c.G,v=e&c.S,g=e&c.P,m=e&c.B,y=e&c.W,h=d?r:r[t]||(r[t]={}),b=h.prototype,x=d?o:v?o[t]:(o[t]||{}).prototype;d&&(n=t);for(l in n)(u=!p&&x&&void 0!==x[l])&&a(h,l)||(f=u?x[l]:n[l],h[l]=d&&"function"!=typeof x[l]?n[l]:m&&u?i(f,o):y&&x[l]==f?function(e){var t=function(t,n,o){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,n)}return new e(t,n,o)}return e.apply(this,arguments)};return t.prototype=e.prototype,t}(f):g&&"function"==typeof f?i(Function.call,f):f,g&&((h.virtual||(h.virtual={}))[l]=f,e&c.R&&b&&!b[l]&&s(b,l,f)))};c.F=1,c.G=2,c.S=4,c.P=8,c.B=16,c.W=32,c.U=64,c.R=128,e.exports=c},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t){e.exports=!0},function(e,t,n){var o=n(7).f,r=n(5),i=n(0)("toStringTag");e.exports=function(e,t,n){e&&!r(e=n?e:e.prototype,i)&&o(e,i,{configurable:!0,value:t})}},function(e,t,n){var o=n(1),r=n(2),i=r["__core-js_shared__"]||(r["__core-js_shared__"]={});(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:o.version,mode:n(20)?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},function(e,t,n){var o=n(41),r=n(8);e.exports=function(e){return o(r(e))}},function(e,t,n){var o=n(13),r=Math.min;e.exports=function(e){return e>0?r(o(e),9007199254740991):0}},function(e,t,n){var o=n(8);e.exports=function(e){return Object(o(e))}},function(e,t){var n=0,o=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+o).toString(36))}},function(e,t){},function(e,t,n){var o=n(61)(n(29),n(62),null,null,null);o.options.__file="/home/sardo/work/vue-booklet/src/components/book.vue",o.esModule&&Object.keys(o.esModule).some(function(e){return"default"!==e&&"__"!==e.substr(0,2)})&&console.error("named exports are not supported in *.vue files."),o.options.functional&&console.error("[vue-loader] book.vue: functional components are not supported with templates, they should use render functions."),e.exports=o.exports},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=n(31),r=n.n(o),i=n(32),s=n.n(i);t.default={name:"Book",data:function(){return{opened:!1,front:!0,back:!1,clickable:!0,totolPages:0,currentPageNum:1}},props:{langcode:{type:String,default:"en"},displayPageNumber:{type:Boolean,default:!0},displayButton:{type:Boolean,default:!0},enableControl:{type:Boolean,default:!0},enableSelectPage:{type:Boolean,default:!0},translation:{default:function(){return s()({en:{selectPage:"Select page",pages:"Pages",prev:"Prev",next:"Next"},"zh-hant":{selectPage:"跳至指定頁數",pages:"頁數",prev:"上一頁",next:"下一頁"},"zh-hans":{selectPage:"跳至指定页数",pages:"页数",prev:"上一页",next:"下一页"}})}},pageTransitionTime:{type:Number,default:.8},onOpened:{type:Function,default:function(){}},onFlipStart:{type:Function,default:function(){}},onFlipEnd:{type:Function,default:function(){}},onClosed:{type:Function,default:function(){}}},computed:{translateText:function(){var e=this.langcode;try{var t=JSON.parse(this.translation)}catch(e){var t=this.translation}return t[e]}},mounted:function(){var e=this,t=this.$refs.book,n=(this.$refs.pages,this.initPage),o=this.selectPage;this.selectPageMobile,this.currentPageNum;if(n(),window.innerWidth<768)for(var r=document.getElementsByClassName("page"),i=0;ie?this.movePage(t,"next"):this.movePage(t,"prev")},movePage:function(e,t){var n=this,o=document.getElementsByClassName("currentPage")[0],i=document.querySelector('[data-index="'+e+'"]'),s=4*this.pageTransitionTime*100;i?(setTimeout(function(){i.classList.remove("fliped")},100),this.opened||this.$emit("onOpened","front")):(i=document.getElementsByClassName("lastPage")[0],i.classList.add("fliped"),this.$emit("onClosed","back"));var a=r()(this.getAllPrevPage(i)),c=r()(this.getAllNextPage(i));o.classList.remove("currentPage"),i.classList.add("currentPage"),i.style.visibility="visible",i.style.zIndex=3,i.classList.contains("firstPage")&&i.nextElementSibling&&this.$emit("onClosed","front"),a.forEach(function(e,t){0===t&&(e.style.zIndex="1"),e.style.visibility="visible",setTimeout(function(){e.classList.add("fliped")},50)}),c.forEach(function(e){e.style.visibility="visible",e.classList.remove("fliped")}),this.$emit("onFlipStart",t),setTimeout(function(){n.$emit("onFlipEnd",t)},s)},selectPageMobile:function(){var e=document.querySelector("#select-page-mobile").value,t=parseInt(e)-1,n=document.querySelector('[data-index="'+e+'"]');n||(n=document.querySelector('[data-index="'+t+'"]')),n.scrollIntoView()},getAllPrevPage:function(e){for(var t=[],n=e.previousElementSibling;n&&!n.classList.contains("control-page");)t.push(n),n=n.previousElementSibling;return t},getAllNextPage:function(e){for(var t=[],n=e.nextElementSibling;n&&!n.classList.contains("control-page");)t.push(n),n=n.nextElementSibling;return t}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=n(28),r=n.n(o),i=n(27);n.n(i);"undefined"!=typeof window&&(window.VueBooklet=r.a),t.default=r.a},function(e,t,n){e.exports={default:n(33),__esModule:!0}},function(e,t,n){e.exports={default:n(34),__esModule:!0}},function(e,t,n){n(58),n(57),e.exports=n(1).Array.from},function(e,t,n){var o=n(1),r=o.JSON||(o.JSON={stringify:JSON.stringify});e.exports=function(e){return r.stringify.apply(r,arguments)}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,n){var o=n(23),r=n(24),i=n(54);e.exports=function(e){return function(t,n,s){var a,c=o(t),l=r(c.length),u=i(s,l);if(e&&n!=n){for(;l>u;)if((a=c[u++])!=a)return!0}else for(;l>u;u++)if((e||u in c)&&c[u]===n)return e||u||0;return!e&&-1}}},function(e,t,n){var o=n(14),r=n(0)("toStringTag"),i="Arguments"==o(function(){return arguments}()),s=function(e,t){try{return e[t]}catch(e){}};e.exports=function(e){var t,n,a;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=s(t=Object(e),r))?n:i?o(t):"Object"==(a=o(t))&&"function"==typeof t.callee?"Arguments":a}},function(e,t,n){"use strict";var o=n(7),r=n(11);e.exports=function(e,t,n){t in e?o.f(e,t,r(0,n)):e[t]=n}},function(e,t,n){var o=n(2).document;e.exports=o&&o.documentElement},function(e,t,n){e.exports=!n(4)&&!n(19)(function(){return 7!=Object.defineProperty(n(16)("div"),"a",{get:function(){return 7}}).a})},function(e,t,n){var o=n(14);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==o(e)?e.split(""):Object(e)}},function(e,t,n){var o=n(10),r=n(0)("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||i[r]===e)}},function(e,t,n){var o=n(3);e.exports=function(e,t,n,r){try{return r?t(o(n)[0],n[1]):t(n)}catch(t){var i=e.return;throw void 0!==i&&o(i.call(e)),t}}},function(e,t,n){"use strict";var o=n(47),r=n(11),i=n(21),s={};n(6)(s,n(0)("iterator"),function(){return this}),e.exports=function(e,t,n){e.prototype=o(s,{next:r(1,n)}),i(e,t+" Iterator")}},function(e,t,n){"use strict";var o=n(20),r=n(18),i=n(52),s=n(6),a=n(10),c=n(44),l=n(21),u=n(49),f=n(0)("iterator"),p=!([].keys&&"next"in[].keys()),d=function(){return this};e.exports=function(e,t,n,v,g,m,y){c(n,t,v);var h,b,x,A=function(e){if(!p&&e in E)return E[e];switch(e){case"keys":case"values":return function(){return new n(this,e)}}return function(){return new n(this,e)}},P=t+" Iterator",_="values"==g,w=!1,E=e.prototype,S=E[f]||E["@@iterator"]||g&&E[g],C=S||A(g),k=g?_?A("entries"):C:void 0,O="Array"==t?E.entries||S:S;if(O&&(x=u(O.call(new e)))!==Object.prototype&&x.next&&(l(x,P,!0),o||"function"==typeof x[f]||s(x,f,d)),_&&S&&"values"!==S.name&&(w=!0,C=function(){return S.call(this)}),o&&!y||!p&&!w&&E[f]||s(E,f,C),a[t]=C,a[P]=d,g)if(h={values:_?C:A("values"),keys:m?C:A("keys"),entries:k},y)for(b in h)b in E||i(E,b,h[b]);else r(r.P+r.F*(p||w),t,h);return h}},function(e,t,n){var o=n(0)("iterator"),r=!1;try{var i=[7][o]();i.return=function(){r=!0},Array.from(i,function(){throw 2})}catch(e){}e.exports=function(e,t){if(!t&&!r)return!1;var n=!1;try{var i=[7],s=i[o]();s.next=function(){return{done:n=!0}},i[o]=function(){return s},e(i)}catch(e){}return n}},function(e,t,n){var o=n(3),r=n(48),i=n(17),s=n(12)("IE_PROTO"),a=function(){},c=function(){var e,t=n(16)("iframe"),o=i.length;for(t.style.display="none",n(39).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write(" 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | Vue Booklet

Vue Booklet

Create your own book without jQuery

-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-booklet", 3 | "version": "2.3.3", 4 | "description": "Vue book-like compoment for create a your own e-book", 5 | "author": "Mr.Twister ", 6 | "main": "dist/lib/vue-booklet.min.js", 7 | "private": false, 8 | "license": "MIT", 9 | "scripts": { 10 | "prepublishOnly": "npm run build", 11 | "dev": "npm-run-all --parallel dev:lib dev:docs", 12 | "dev:lib": "webpack --config build/webpack.lib.conf.js --watch --progress --hide-modules", 13 | "dev:docs": "node build/dev-server.js", 14 | "build": "npm run build:lib && npm run build:docs", 15 | "build:lib": "node build/build-lib.js", 16 | "build:docs": "node build/build.js" 17 | }, 18 | "dependencies": { 19 | "js-yaml": "^3.13.1", 20 | "serialize-javascript": "^5.0.1", 21 | "vue": "^2.3.3", 22 | "yargs-parser": "^13.1.2" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^7.1.2", 26 | "babel-core": "^6.22.1", 27 | "babel-loader": "^7.1.1", 28 | "babel-plugin-transform-runtime": "^6.22.0", 29 | "babel-preset-env": "^1.3.2", 30 | "babel-preset-stage-2": "^6.22.0", 31 | "babel-register": "^6.22.0", 32 | "chalk": "^2.0.1", 33 | "connect-history-api-fallback": "^1.3.0", 34 | "copy-webpack-plugin": "^4.0.1", 35 | "css-loader": "^0.28.0", 36 | "cssnano": "^3.10.0", 37 | "eventsource-polyfill": "^0.9.6", 38 | "express": "^4.14.1", 39 | "extract-text-webpack-plugin": "^2.0.0", 40 | "file-loader": "^0.11.1", 41 | "friendly-errors-webpack-plugin": "^1.1.3", 42 | "html-webpack-plugin": "^2.28.0", 43 | "http-proxy-middleware": "^0.17.3", 44 | "node-sass": "^7.0.0", 45 | "npm-run-all": "^4.1.5", 46 | "opn": "^5.1.0", 47 | "optimize-css-assets-webpack-plugin": "^2.0.0", 48 | "ora": "^1.2.0", 49 | "rimraf": "^2.6.0", 50 | "sass-loader": "^6.0.5", 51 | "semver": "^5.3.0", 52 | "shelljs": "^0.8.5", 53 | "url-loader": "^0.5.8", 54 | "vue-loader": "^12.1.0", 55 | "vue-style-loader": "^3.0.1", 56 | "vue-template-compiler": "^2.3.3", 57 | "webpack": "^2.6.1", 58 | "webpack-bundle-analyzer": "^4.2.0", 59 | "webpack-dev-middleware": "^1.10.0", 60 | "webpack-hot-middleware": "^2.18.0", 61 | "webpack-merge": "^4.1.0" 62 | }, 63 | "engines": { 64 | "node": ">= 4.0.0", 65 | "npm": ">= 3.0.0" 66 | }, 67 | "browserslist": [ 68 | "> 1%", 69 | "last 2 versions", 70 | "not ie <= 8" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /src/app.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 95 | -------------------------------------------------------------------------------- /src/assets/arrow_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falconshark/vue-booklet/c4d88d4520112718ba3310fe6af06a8c62e3e019/src/assets/arrow_left.png -------------------------------------------------------------------------------- /src/assets/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falconshark/vue-booklet/c4d88d4520112718ba3310fe6af06a8c62e3e019/src/assets/arrow_right.png -------------------------------------------------------------------------------- /src/assets/black_cat_black_and_white-300px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falconshark/vue-booklet/c4d88d4520112718ba3310fe6af06a8c62e3e019/src/assets/black_cat_black_and_white-300px.png -------------------------------------------------------------------------------- /src/components/book.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 509 | -------------------------------------------------------------------------------- /src/docs.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 | 6 | import './styles/app.scss' 7 | 8 | Vue.config.productionTip = false 9 | 10 | /* eslint-disable no-new */ 11 | new Vue({ 12 | el: '#app', 13 | template: '', 14 | components: { App } 15 | }) 16 | -------------------------------------------------------------------------------- /src/lib.js: -------------------------------------------------------------------------------- 1 | import Book from './components/book.vue'; 2 | import './styles/lib.scss'; 3 | 4 | if (typeof window !== 'undefined') { 5 | window.VueBooklet = Book; 6 | } 7 | 8 | export default Book 9 | -------------------------------------------------------------------------------- /src/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import 'lib'; 2 | 3 | html, body{ 4 | height: 100%; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | .container{ 10 | height: 80%; 11 | } 12 | 13 | #app{ 14 | width: 100%; 15 | height: 100%; 16 | } 17 | 18 | .cover{ 19 | h1{ 20 | margin-bottom: 1em; 21 | text-align: center; 22 | } 23 | 24 | img{ 25 | margin-left: auto; 26 | margin-right: auto; 27 | } 28 | 29 | @media (min-width: 768px) { 30 | img{ 31 | max-width: 300px; 32 | } 33 | } 34 | } 35 | 36 | #index{ 37 | .website-content{ 38 | text-align: center; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/styles/lib.scss: -------------------------------------------------------------------------------- 1 | .cover{ 2 | background-color: #BCA98A; 3 | } 4 | 5 | .book-control-buttons{ 6 | display: none; 7 | } 8 | 9 | .control-page{ 10 | display: none; 11 | } 12 | 13 | .book{ 14 | .page{ 15 | .content{ 16 | border: 1px solid #ddd; 17 | padding: 5%; 18 | margin-bottom: 0.5em; 19 | text-align: left; 20 | } 21 | } 22 | } 23 | 24 | .select-page-wrapper{ 25 | display: none; 26 | margin-top: 1em; 27 | font-size: 1.2em; 28 | text-align: center; 29 | 30 | select{ 31 | margin-left: 1em; 32 | width: 20%; 33 | } 34 | } 35 | 36 | .select-page-wrapper-mobile{ 37 | display: block; 38 | text-align: center; 39 | font-size: 1.2em; 40 | margin-top: 1.2em; 41 | margin-bottom: 1em; 42 | 43 | select{ 44 | margin-left: 1em; 45 | width: 20%; 46 | } 47 | } 48 | 49 | .page-number{ 50 | display: none; 51 | margin-top: 1em; 52 | margin-bottom: 1em; 53 | font-size: 1.2em; 54 | text-align: center; 55 | } 56 | 57 | @media (min-width: 768px) { 58 | .vue-booklet{ 59 | width: 100%; 60 | height: 100vh; 61 | max-height: 650px; 62 | margin: 0.5em; 63 | -webkit-transform-style: preserve-3d; 64 | -moz-transform-style: preserve-3d; 65 | transform-style: preserve-3d; 66 | } 67 | 68 | .book{ 69 | font-family: Book Antiqua, Palatino, Palatino Linotype, Palatino LT STD, Georgia, serif; 70 | position: relative; 71 | width: 100%; 72 | height: 100%; 73 | -webkit-transform-style: preserve-3d; 74 | -moz-transform-style: preserve-3d; 75 | transform-style: preserve-3d; 76 | transition: all .8s; 77 | } 78 | 79 | .control-page{ 80 | position: absolute; 81 | display: flex; 82 | align-items:center; 83 | justify-content: center; 84 | width: 100px; 85 | height: 100%; 86 | z-index: 99; 87 | transition: all .3s; 88 | opacity: 0; 89 | background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); 90 | background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); 91 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); 92 | background-repeat: repeat-x; 93 | 94 | &:hover{ 95 | opacity: 0.5; 96 | } 97 | } 98 | 99 | .control-page-left{ 100 | left: 0; 101 | } 102 | 103 | .control-page-right{ 104 | right: 0; 105 | } 106 | 107 | .book-control-buttons{ 108 | display: block; 109 | text-align: center; 110 | } 111 | 112 | .book-control-button{ 113 | width: 75px; 114 | height: 40px; 115 | border: 0; 116 | border-radius: 2px; 117 | color: #ffffff; 118 | background-color: #72B890; 119 | font-size: 16px; 120 | transition: all 0.3s; 121 | margin-top: 1em; 122 | margin-bottom: 1em; 123 | margin-right: 1em; 124 | 125 | &:hover, &:focus{ 126 | color: #ffffff; 127 | background-color: #759C86; 128 | border-color: #72B890; 129 | } 130 | } 131 | 132 | .closed{ 133 | -webkit-transform: translateX(-25%); 134 | transform: translateX(-25%); 135 | } 136 | 137 | .closed-back{ 138 | -webkit-transform: translateX(25%); 139 | transform: translateX(25%); 140 | } 141 | 142 | .opened{ 143 | -webkit-transform: translateX(-1vw); 144 | transform: translateX(-1vw); 145 | } 146 | 147 | .page-number{ 148 | display: block; 149 | } 150 | 151 | .select-page-wrapper{ 152 | display: block; 153 | } 154 | 155 | .select-page-wrapper-mobile{ 156 | display: none; 157 | } 158 | 159 | .page{ 160 | position: absolute; 161 | width: 50%; 162 | height: 100%; 163 | color: #222; 164 | background-color: white; 165 | top: 0; 166 | right: 0; 167 | transform-origin: 0 50%; 168 | border: 1px solid #ddd; 169 | background: linear-gradient(to right, #d9d9d9 0%,#f9f9f9 3%,#ffffff 8%,#ffffff 100%); 170 | -webkit-transform-style: preserve-3d; 171 | -moz-transform-style: preserve-3d; 172 | transform-style: preserve-3d; 173 | 174 | .content{ 175 | position: absolute; 176 | width: 100%; 177 | max-width: 100%; 178 | height: 100%; 179 | padding: 5%; 180 | border: none; 181 | transform-origin: center center; 182 | backface-visibility: hidden; 183 | overflow: auto; 184 | -webkit-transform-style: preserve-3d; 185 | -moz-transform-style: preserve-3d; 186 | transform-style: preserve-3d; 187 | 188 | &:nth-child(1){ 189 | -webkit-transform: translateZ(1px); 190 | -moz-transform: translateZ(1px); 191 | transform: translateZ(1px); 192 | } 193 | 194 | &:nth-child(2){ 195 | -webkit-transform: rotateY(180deg) translateZ(1px); 196 | -moz-transform: rotateY(180deg) translateZ(1px); 197 | transform: rotateY(180deg) translateZ(1px); 198 | } 199 | } 200 | } 201 | 202 | .pages{ 203 | position: relative; 204 | height: 100%; 205 | } 206 | 207 | .cover{ 208 | background: none; 209 | background-color: #BCA98A; 210 | } 211 | 212 | .fliped{ 213 | -webkit-transform: rotateY(-180deg) translateZ(1px); 214 | transform: rotateY(-180deg) translateZ(1px); 215 | } 216 | 217 | .progress{ 218 | width:100%; 219 | height:20px; 220 | background-color: #ccc; 221 | overflow: hidden; 222 | cursor: pointer; 223 | } 224 | .left{ 225 | height:100%; 226 | width: 30%; 227 | background-color: greenyellow; 228 | position: relative; 229 | } 230 | .ball{ 231 | height:100%; 232 | width:20px; 233 | -webkit-border-radius:10px; 234 | -moz-border-radius:10px; 235 | border-radius:10px; 236 | background-color: red; 237 | position: absolute; 238 | right: -10px; 239 | } 240 | } 241 | 242 | .back{ 243 | background: none; 244 | background-color: #BCA98A; 245 | } 246 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falconshark/vue-booklet/c4d88d4520112718ba3310fe6af06a8c62e3e019/static/.gitkeep --------------------------------------------------------------------------------