├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .flowconfig
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── config
├── .eslintrc
├── banner.js
├── build.js
├── bundle.js
├── entry.js
├── karma.base.conf.js
├── karma.cover.conf.js
├── karma.sauce.conf.js
├── karma.unit.conf.js
├── nightwatch.conf.js
├── version.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── dist
├── vue-h-zoom.js
└── vue-h-zoom.min.js
├── docs
└── vue-h-zoom-preview.jpg
├── package-lock.json
├── package.json
├── src
├── assets
│ ├── bugatti-chiron-white_01.jpg
│ └── bugatti-chiron-white_01_thumb.jpg
├── demo.js
├── index.js
└── libs
│ ├── VueHZoom.vue
│ └── vue-h-zoom.js
└── test
├── .eslintrc
├── e2e
├── runner.js
└── test
│ └── add.js
├── helpers
├── assert.js
├── entry.js
└── wait-for-update.js
└── unit
├── index.html
├── index.js
└── vue-h-zoom.test.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "flow-vue"],
3 | "plugins": ["babel-plugin-espower"],
4 | "env": {
5 | "test": {
6 | "plugins": ["istanbul"],
7 | "presets": ["power-assert"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.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 | config/*.js
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "plugins": [
4 | "flowtype"
5 | ],
6 | "extends": [
7 | "plugin:vue-libs/recommended",
8 | "plugin:flowtype/recommended"
9 | ],
10 | "rules": {
11 | "object-curly-spacing": ["error", "always"],
12 | "no-multiple-empty-lines": ["error", { "max": 2, "maxBOF": 1 }],
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | .*/node_modules/.*
3 | .*/docs/.*
4 | .*/test/.*
5 | .*/config/.*
6 | .*/examples/.*
7 |
8 | [include]
9 |
10 | [libs]
11 | decls
12 |
13 | [options]
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 | ### vue & vue-h-zoom version
32 | 2.0.x, x.y.z
33 |
34 | ### Reproduction Link
35 |
36 |
37 | ### Steps to reproduce
38 |
39 | ### What is Expected?
40 |
41 | ### What is actually happening?
42 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bliblidotcom/vue-h-zoom/ecc385f89bc707a814e51ce951aa16b58bebe588/.github/PULL_REQUEST_TEMPLATE.md
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | coverage
3 | dist/*.gz
4 | docs/_book
5 | test/e2e/report
6 | test/e2e/screenshots
7 | node_modules
8 | .DS_Store
9 | *.log
10 | *.swp
11 | *~
12 | .idea
13 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*
2 | *.log
3 | *.swp
4 | *.yml
5 | coverage
6 | docs/_book
7 | config
8 | dist/*.map
9 | lib
10 | test
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | before_install:
5 | - npm install -g phantomjs
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bliblidotcom/vue-h-zoom/ecc385f89bc707a814e51ce951aa16b58bebe588/CHANGELOG.md
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # vue-h-zoom Contributing Guide
2 |
3 | - [Issue Reporting Guidelines](#issue-reporting-guidelines)
4 | - [Pull Request Guidelines](#pull-request-guidelines)
5 | - [Development Setup](#development-setup)
6 |
7 | ## Issue Reporting Guidelines
8 |
9 | - The issue list of this repo is **exclusively** for bug reports and feature requests. Non-conforming issues will be closed immediately.
10 |
11 | - For simple beginner questions, you can get quick answers from [The Gitter chat room](https://gitter.im/vuejs/vue).
12 |
13 | - For more complicated questions, you can use [the official forum](http://forum.vuejs.org/) or StackOverflow. Make sure to provide enough information when asking your questions - this makes it easier for others to help you!
14 |
15 | - Try to search for your issue, it may have already been answered or even fixed in the development branch.
16 |
17 | - Check if the issue is reproducible with the latest stable version of Vue. If you are using a pre-release, please indicate the specific version you are using.
18 |
19 | - It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Issues with no clear repro steps will not be triaged. If an issue labeled "need repro" receives no further input from the issue author for more than 5 days, it will be closed.
20 |
21 | - It is recommended that you make a JSFiddle/JSBin/Codepen to demonstrate your issue. You could start with [this template](http://jsfiddle.net/5sH6A/) that already includes the latest version of Vue.
22 |
23 | - For bugs that involves build setups, you can create a reproduction repository with steps in the README.
24 |
25 | - If your issue is resolved but still open, don’t hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it.
26 |
27 | ## Pull Request Guidelines
28 |
29 | - The `master` branch is basically just a snapshot of the latest stable release. All development should be done in dedicated branches. **Do not submit PRs against the `master` branch.**
30 |
31 | - Checkout a topic branch from the relevant branch, e.g. `dev`, and merge back against that branch.
32 |
33 | - Work in the `src` folder and **DO NOT** checkin `dist` in the commits.
34 |
35 | - It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging.
36 |
37 | - Make sure `npm test` passes. (see [development setup](#development-setup))
38 |
39 | - If adding new feature:
40 | - Add accompanying test case.
41 | - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it.
42 |
43 | - If fixing a bug:
44 | - Provide detailed description of the bug in the PR. Live demo preferred.
45 | - Add appropriate test coverage if applicable.
46 |
47 | ### Work Step Example
48 | - Fork the repository from [hidayat.febiansyah/vue-h-zoom](https://github.com/hidayat.febiansyah/vue-h-zoom) !
49 | - Create your topic branch from `dev`: `git branch my-new-topic origin/dev`
50 | - Add codes and pass tests !
51 | - Commit your changes: `git commit -am 'Add some topic'`
52 | - Push to the branch: `git push origin my-new-topic`
53 | - Submit a pull request to `dev` branch of `hidayat.febiansyah/vue-h-zoom` repository !
54 |
55 | ## Development Setup
56 |
57 | You will need [Node.js](http://nodejs.org) and [Java Runtime Environment](http://www.oracle.com/technetwork/java/javase/downloads/index.html) (needed for running Selenium server during e2e tests).
58 |
59 | After cloning the repo, run:
60 |
61 | $ npm install
62 |
63 | ### Commonly used NPM scripts
64 |
65 | # watch and serve with hot reload unit test at localhost:8080
66 | $ npm run dev
67 |
68 | # lint source codes
69 | $ npm run lint
70 |
71 | # run unit tests in browser (firefox/safari/chrome)
72 | $ npm run test:unit
73 |
74 | # build all dist files, including npm packages
75 | $ npm run build
76 |
77 | # run the full test suite, include linting / type checking
78 | $ npm test
79 |
80 | There are some other scripts available in the `scripts` section of the `package.json` file.
81 |
82 | The default test script will do the following: lint with ESLint -> type check with Flow -> unit tests with coverage -> e2e tests. **Please make sure to have this pass successfully before submitting a PR.** Although the same tests will be run against your PR on the CI server, it is better to have it working locally beforehand.
83 |
84 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 hidayat.febiansyah
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-h-zoom
2 |
3 |
4 | [](https://codecov.io/gh/bliblidotcom/vue-h-zoom)
5 | [](https://www.npmjs.com/package/vue-h-zoom)
6 | [](https://vuejs.org/)
7 |
8 | Vue Native Zoom images
9 |
10 | ## Installation
11 |
12 | ```js
13 | npm i --save-dev vue-h-zoom
14 | ```
15 |
16 | ## About
17 |
18 | This plugin is intended to provide native implementation of zooming library. It support thumbnail image and full size
19 | image as parameter. Location of zooming preview is configurable through absolute location.
20 |
21 | ### Browser
22 |
23 | Include the script file, then install the component with `Vue.use(VueHZoom);` e.g.:
24 |
25 | ```html
26 |
27 |
28 |
31 | ```
32 |
33 | ### Module
34 |
35 | ```js
36 | import VueHZoom from 'vue-h-zoom';
37 | ```
38 |
39 | ## Usage
40 |
41 | Use in template for example as:
42 |
43 | ```html
44 |
47 | ```
48 |
49 | ## Important notes
50 |
51 | To be able to import image locally, you need to register the vue-h-zoom tag to the vue loader, add this to your
52 | webpack config:
53 |
54 | ``` js
55 | {
56 | test: /\.vue$/,
57 | loader: 'vue-loader',
58 | options: {
59 | loaders: {
60 | },
61 | // other vue-loader options go here
62 | transformToRequire: {
63 | 'vue-h-zoom': ['image', 'image-full']
64 | }
65 | }
66 | },
67 | ```
68 |
69 | ## Parameters
70 |
71 | | Attribute | Type | Default | Value | Description |
72 | | :--- | :---: | :---: | :--- | :--- |
73 | | image | String | - | - | Image to be displayed in thumbnail. Used also in the zoom if imageFull param is not given (required) |
74 | | image-full | String | '' | - | Large version of image|
75 | | width | Number | 200 | - | Width of thumbnail in px|
76 | | height | Number | 200 | - | Height of thumbnail in px|
77 | | zoom-level | Number | 4 | - | Zoom level |
78 | | zoom-window-size | Number | 2 | - | Zoom window size multiplier, relative with thumbnail size |
79 | | zoom-window-x | Number | 300 | - | Location absolute on x-axis for zoom window |
80 | | zoom-window-y | Number | 300 | - | Location absolute on y-axis for zoom window |
81 | | background-options | Object | null | `BACKGROUND_OPTIONS object` or `{}` or `true` | Options to create custom background. Please refer to next section for available properties |
82 |
83 | ### BACKGROUND_OPTIONS
84 |
85 | | Properties | Type | Default | Description |
86 | | :--- | :--- | :--- | :--- |
87 | | image | String | 'none' | Image url to be used as background |
88 | | color | String | '#fff' | Color to be used in background, use any value compatible with `background-color` css property |
89 | | repeat | Boolean | false | Option to repeat the background image |
90 | | size | String | '100%' | Set the size of background image, use any value compatible with `background-size` css property |
91 | | position | String | 'top left' | Set the position of background image, use any value compatible with `background-position` css property |
92 |
93 | Additional notes:
94 | * You can specify truthy value to use default background options, such as
95 | `` or ``
96 | * You only need to specify property (s) that you need, such as ``
97 | * Using background-options automatically update the image size property to `background-size: contain;`, instead of the default `background-size: cover;`. This is done so that the background is also included in zoomed preview.
98 |
99 | ## Preview
100 |
101 |
102 |
103 |
104 |
105 |
106 | ## :copyright: License
107 |
108 | [MIT](http://opensource.org/licenses/MIT)
109 |
--------------------------------------------------------------------------------
/config/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "process": true
4 | },
5 | "extends": "vue",
6 | "rules": {
7 | "no-multiple-empty-lines": [2, {"max": 2}],
8 | "no-console": 0
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/config/banner.js:
--------------------------------------------------------------------------------
1 | const pack = require('../package.json')
2 | const version = process.env.VERSION || pack.version
3 |
4 | module.exports =
5 | '/*!\n' +
6 | ` * ${pack.name} v${version} \n` +
7 | ` * (c) ${new Date().getFullYear()} ${pack.author.name}\n` +
8 | ` * Released under the ${pack.license} License.\n` +
9 | ' */'
10 |
--------------------------------------------------------------------------------
/config/build.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const exist = fs.existsSync
3 | const mkdir = fs.mkdirSync
4 | const getAllEntries = require('./entry').getAllEntries
5 | const build = require('./bundle')
6 |
7 | if (!exist('dist')) {
8 | mkdir('dist')
9 | }
10 |
11 | let entries = getAllEntries()
12 |
13 | // filter entries via command line arg
14 | if (process.argv[2]) {
15 | const filters = process.argv[2].split(',')
16 | entries = entries.filter(b => {
17 | return filters.some(f => b.dest.indexOf(f) > -1)
18 | })
19 | }
20 |
21 | build(entries)
22 |
--------------------------------------------------------------------------------
/config/bundle.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const readFile = fs.readFile
3 | const writeFile = fs.writeFile
4 | const relative = require('path').relative
5 | const gzip = require('zlib').gzip
6 | const rollup = require('rollup')
7 | const uglify = require('uglify-js')
8 |
9 | module.exports = build
10 |
11 | function build (entries) {
12 | let built = 0
13 | const total = entries.length
14 | const next = () => {
15 | buildEntry(entries[built]).then(() => {
16 | built++
17 | if (built < total) {
18 | next()
19 | }
20 | }).catch(logError)
21 | }
22 | next()
23 | }
24 |
25 | function buildEntry (config) {
26 | const isProd = /min\.js$/.test(config.dest)
27 | return rollup.rollup(config).then(bundle => {
28 | const code = bundle.generate(config).code
29 | if (isProd) {
30 | var minified = (config.banner ? config.banner + '\n' : '') + uglify.minify(code, {
31 | fromString: true,
32 | output: {
33 | screw_ie8: true,
34 | ascii_only: true
35 | },
36 | compress: {
37 | pure_funcs: ['makeMap']
38 | }
39 | }).code
40 | return write(config.dest, minified).then(zip(config.dest))
41 | } else {
42 | return write(config.dest, code)
43 | }
44 | })
45 | }
46 |
47 | function write (dest, code) {
48 | return new Promise(function (resolve, reject) {
49 | writeFile(dest, code, function (err) {
50 | if (err) { return reject(err) }
51 | console.log(blue(relative(process.cwd(), dest)) + ' ' + getSize(code))
52 | resolve()
53 | })
54 | })
55 | }
56 |
57 | function zip (file) {
58 | return function () {
59 | return new Promise(function (resolve, reject) {
60 | readFile(file, function (err, buf) {
61 | if (err) { return reject(err) }
62 | gzip(buf, function (err, buf) {
63 | if (err) { return reject(err) }
64 | write(file + '.gz', buf).then(resolve)
65 | })
66 | })
67 | })
68 | }
69 | }
70 |
71 | function getSize (code) {
72 | return (code.length / 1024).toFixed(2) + 'kb'
73 | }
74 |
75 | function logError (e) {
76 | console.log(e)
77 | }
78 |
79 | function blue (str) {
80 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
81 | }
82 |
--------------------------------------------------------------------------------
/config/entry.js:
--------------------------------------------------------------------------------
1 | const replace = require('rollup-plugin-replace')
2 | const flow = require('rollup-plugin-flow-no-whitespace')
3 | const buble = require('rollup-plugin-buble')
4 | const banner = require('./banner')
5 | const pack = require('../package.json')
6 |
7 | function toUpper (_, c) {
8 | return c ? c.toUpperCase() : ''
9 | }
10 |
11 | const classifyRE = /(?:^|[-_\/])(\w)/g
12 | function classify (str) {
13 | return str.replace(classifyRE, toUpper)
14 | }
15 | const moduleName = classify(pack.name)
16 |
17 | const entries = {
18 | commonjs: {
19 | entry: 'src/index.js',
20 | dest: `dist/${pack.name}.common.js`,
21 | format: 'cjs',
22 | banner
23 | },
24 | esm: {
25 | entry: 'src/index.js',
26 | dest: `dist/${pack.name}.esm.js`,
27 | format: 'es',
28 | banner
29 | },
30 | production: {
31 | entry: 'src/index.js',
32 | dest: `dist/${pack.name}.min.js`,
33 | format: 'umd',
34 | env: 'production',
35 | moduleName,
36 | banner
37 | },
38 | development: {
39 | entry: 'src/index.js',
40 | dest: `dist/${pack.name}.js`,
41 | format: 'umd',
42 | env: 'development',
43 | moduleName,
44 | banner
45 | }
46 | }
47 |
48 | function genConfig (opts) {
49 | const config = {
50 | entry: opts.entry,
51 | dest: opts.dest,
52 | format: opts.format,
53 | banner: opts.banner,
54 | moduleName,
55 | plugins: [
56 | flow(),
57 | buble()
58 | ]
59 | }
60 |
61 | const replacePluginOptions = { '__VERSION__': pack.version }
62 | if (opts.env) {
63 | replacePluginOptions['process.env.NODE_ENV'] = JSON.stringify(opts.env)
64 | }
65 | config.plugins.push(replace(replacePluginOptions))
66 |
67 | return config
68 | }
69 |
70 | exports.getEntry = name => genConfig(entries[name])
71 | exports.getAllEntries = () => Object.keys(entries).map(name => genConfig(entries[name]))
72 |
--------------------------------------------------------------------------------
/config/karma.base.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 |
4 | function resolve (dir) {
5 | return path.join(__dirname, '..', dir)
6 | }
7 |
8 | const webpackConfig = {
9 | resolve: {
10 | extensions: ['.js', '.vue'],
11 | alias: {
12 | vue: 'vue/dist/vue.js',
13 | '@': resolve('src')
14 | }
15 | },
16 | module: {
17 | loaders: [{
18 | test: /\.js$/,
19 | exclude: /node_modules|vue\/dist/,
20 | loader: 'babel-loader'
21 | },
22 | {
23 | test: /\.vue$/,
24 | loader: 'vue-loader',
25 | options: {
26 | loaders: {
27 | js: 'babel-loader!eslint-loader'
28 | }
29 | }
30 | }]
31 | },
32 | plugins: [
33 | new webpack.DefinePlugin({
34 | 'process.env': {
35 | NODE_ENV: '"development"'
36 | }
37 | })
38 | ],
39 | devtool: '#inline-source-map'
40 | }
41 |
42 | module.exports = {
43 | basePath: '',
44 | files: [
45 | '../test/unit/index.js'
46 | ],
47 | exclude: [
48 | ],
49 | frameworks: ['mocha'],
50 | preprocessors: {
51 | '../test/unit/index.js': ['webpack', 'sourcemap']
52 | },
53 | webpack: webpackConfig,
54 | webpackMiddleware: {
55 | noInfo: true
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/config/karma.cover.conf.js:
--------------------------------------------------------------------------------
1 | const base = require('./karma.base.conf')
2 |
3 | module.exports = config => {
4 | const options = Object.assign(base, {
5 | browsers: ['ChromeHeadless'],
6 | reporters: ['mocha', 'coverage'],
7 | coverageReporter: {
8 | reporters: [
9 | { type: 'lcov', dir: '../coverage', subdir: '.' },
10 | { type: 'text-summary', dir: '../coverage', subdir: '.' }
11 | ]
12 | },
13 | singleRun: true
14 | })
15 |
16 | config.set(options)
17 | }
18 |
--------------------------------------------------------------------------------
/config/karma.sauce.conf.js:
--------------------------------------------------------------------------------
1 | const base = require('./karma.base.conf')
2 | const pack = require('../package.json')
3 |
4 | /**
5 | * Having too many tests running concurrently on saucelabs
6 | * causes timeouts and errors, so we have to run them in
7 | * smaller batches.
8 | */
9 |
10 | const batches = [
11 | // the coolkids
12 | {
13 | sl_chrome: {
14 | base: 'SauceLabs',
15 | browserName: 'chrome',
16 | platform: 'Windows 7'
17 | },
18 | sl_firefox: {
19 | base: 'SauceLabs',
20 | browserName: 'firefox'
21 | },
22 | sl_mac_safari: {
23 | base: 'SauceLabs',
24 | browserName: 'safari',
25 | platform: 'OS X 10.10'
26 | }
27 | },
28 | // ie family
29 | {
30 | sl_ie_9: {
31 | base: 'SauceLabs',
32 | browserName: 'internet explorer',
33 | platform: 'Windows 7',
34 | version: '9'
35 | },
36 | sl_ie_10: {
37 | base: 'SauceLabs',
38 | browserName: 'internet explorer',
39 | platform: 'Windows 8',
40 | version: '10'
41 | },
42 | sl_ie_11: {
43 | base: 'SauceLabs',
44 | browserName: 'internet explorer',
45 | platform: 'Windows 8.1',
46 | version: '11'
47 | },
48 | sl_edge: {
49 | base: 'SauceLabs',
50 | platform: 'Windows 10',
51 | browserName: 'MicrosoftEdge'
52 | }
53 | },
54 | // mobile
55 | {
56 | sl_ios_safari_8: {
57 | base: 'SauceLabs',
58 | browserName: 'iphone',
59 | version: '8.4'
60 | },
61 | sl_ios_safari_9: {
62 | base: 'SauceLabs',
63 | browserName: 'iphone',
64 | version: '9.3'
65 | },
66 | sl_android_4_2: {
67 | base: 'SauceLabs',
68 | browserName: 'android',
69 | version: '4.2'
70 | },
71 | sl_android_5_1: {
72 | base: 'SauceLabs',
73 | browserName: 'android',
74 | version: '5.1'
75 | }
76 | }
77 | ]
78 |
79 | module.exports = config => {
80 | const batch = batches[process.argv[5] || 0]
81 |
82 | config.set(Object.assign(base, {
83 | singleRun: true,
84 | browsers: Object.keys(batch),
85 | customLaunchers: batch,
86 | reporters: process.env.CI
87 | ? ['dots', 'saucelabs'] // avoid spamming CI output
88 | : ['progress', 'saucelabs'],
89 | sauceLabs: {
90 | testName: `${pack.name} unit tests`,
91 | recordScreenshots: false,
92 | build: process.env.CIRCLE_BUILD_NUM || process.env.SAUCE_BUILD_ID || Date.now()
93 | },
94 | captureTimeout: 300000,
95 | browserNoActivityTimeout: 300000
96 | }))
97 | }
98 |
--------------------------------------------------------------------------------
/config/karma.unit.conf.js:
--------------------------------------------------------------------------------
1 | const base = require('./karma.base.conf')
2 |
3 | module.exports = config => {
4 | config.set(Object.assign(base, {
5 | reporters: ['spec', 'progress'],
6 | browsers: ['Chrome', 'Firefox', 'Safari'],
7 | singleRun: true
8 | }))
9 | }
10 |
--------------------------------------------------------------------------------
/config/nightwatch.conf.js:
--------------------------------------------------------------------------------
1 | // http://nightwatchjs.org/guide#settings-file
2 | module.exports = {
3 | src_folders: ['test/e2e/test'],
4 | output_folder: 'test/e2e/report',
5 | custom_commands_path: ['node_modules/nightwatch-helpers/commands'],
6 | custom_assertions_path: ['node_modules/nightwatch-helpers/assertions'],
7 |
8 | selenium: {
9 | start_process: true,
10 | server_path: 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar',
11 | host: '127.0.0.1',
12 | port: 4444,
13 | cli_args: {
14 | 'webdriver.chrome.driver': require('chromedriver').path
15 | }
16 | },
17 |
18 | test_settings: {
19 | default: {
20 | selenium_port: 4444,
21 | selenium_host: 'localhost',
22 | silent: true,
23 | screenshots: {
24 | enabled: true,
25 | on_failure: true,
26 | on_error: false,
27 | path: 'test/e2e/screenshots'
28 | }
29 | },
30 |
31 | chrome: {
32 | desiredCapabilities: {
33 | browserName: 'chrome',
34 | javascriptEnabled: true,
35 | acceptSslCerts: true
36 | }
37 | },
38 |
39 | firefox: {
40 | desiredCapabilities: {
41 | browserName: 'firefox',
42 | javascriptEnabled: true,
43 | acceptSslCerts: true
44 | }
45 | },
46 |
47 | headless: {
48 | desiredCapabilities: {
49 | browserName: 'chrome',
50 | chromeOptions : {
51 | args : ['headless']
52 | },
53 | javascriptEnabled: true,
54 | acceptSslCerts: true
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/config/version.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const pack = require('../package.json')
3 |
4 | // update installation.md
5 | const installation = fs
6 | .readFileSync('./gitbook/installation.md', 'utf-8')
7 | .replace(
8 | /https:\/\/unpkg\.com\/vue-h-zoom@[\d.]+.[\d]+\/dist\/vue-h-zoom\.js/,
9 | 'https://unpkg.com/vue-h-zoom@' + pack.version + '/dist/vue-h-zoom.js.'
10 | )
11 | fs.writeFileSync('./gitbook/installation.md', installation)
12 |
--------------------------------------------------------------------------------
/config/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const CopyWebpackPlugin = require('copy-webpack-plugin')
4 |
5 | function resolve (dir) {
6 | return path.join(__dirname, '..', dir)
7 | }
8 |
9 | module.exports = {
10 | entry: {
11 | tests: 'mocha-loader!./test/unit/index.js',
12 | demo: './src/demo.js'
13 | },
14 | resolve: {
15 | extensions: ['.js', '.vue'],
16 | alias: {
17 | vue: 'vue/dist/vue.js',
18 | '@': resolve('src')
19 | }
20 | },
21 | output: {
22 | path: path.resolve(__dirname, '/test/unit'),
23 | filename: '[name].js',
24 | publicPath: '/'
25 | },
26 | module: {
27 | rules: [{
28 | test: /\.js$/,
29 | exclude: /node_modules|vue\/dist/,
30 | loader: 'babel-loader'
31 | },
32 | {
33 | test: /\.vue$/,
34 | loader: 'vue-loader',
35 | options: {
36 | loaders: {
37 | js: 'babel-loader!eslint-loader'
38 | }
39 | }
40 | }]
41 | },
42 | plugins: [
43 | new webpack.DefinePlugin({
44 | 'process.env': {
45 | NODE_ENV: '"development"'
46 | }
47 | }),
48 | new CopyWebpackPlugin([ { from: 'src/assets', to: 'assets' } ])
49 | ],
50 | devtool: '#eval-source-map'
51 | }
52 |
--------------------------------------------------------------------------------
/config/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const merge = require('webpack-merge')
4 | const CopyWebpackPlugin = require('copy-webpack-plugin')
5 |
6 | function resolve (dir) {
7 | return path.join(__dirname, '..', dir)
8 | }
9 |
10 | const commonConfig = {
11 | resolve: {
12 | extensions: ['.js', '.vue'],
13 | alias: {
14 | vue: 'vue/dist/vue.js',
15 | '@': resolve('src')
16 | }
17 | },
18 | output: {
19 | path: resolve('dist/')
20 | },
21 | module: {
22 | rules: [{
23 | test: /\.js$/,
24 | exclude: /node_modules|vue\/dist/,
25 | loader: 'babel-loader'
26 | },
27 | {
28 | test: /\.vue$/,
29 | loader: 'vue-loader',
30 | options: {
31 | loaders: {
32 | js: 'babel-loader!eslint-loader'
33 | }
34 | }
35 | },
36 | {
37 | test: /\.css$/,
38 | loader: 'style!less!css'
39 | }]
40 | },
41 | externals: {
42 | vue: 'vue'
43 | },
44 | plugins: [
45 | new webpack.DefinePlugin({
46 | 'process.env': {
47 | NODE_ENV: '"production"'
48 | }
49 | }),
50 | new webpack.optimize.UglifyJsPlugin( {
51 | minimize : true,
52 | sourceMap : false,
53 | mangle: true,
54 | compress: {
55 | warnings: false
56 | }
57 | })
58 | ]
59 | }
60 | module.exports = [
61 | merge(commonConfig, {
62 | entry: resolve('src/index.js'),
63 | output: {
64 | filename: 'vue-h-zoom.min.js',
65 | libraryTarget: 'window',
66 | library: 'VueHZoom',
67 | }
68 | }),
69 | merge(commonConfig, {
70 | entry: resolve('src/libs/VueHZoom.vue'),
71 | output: {
72 | filename: 'vue-h-zoom.js',
73 | libraryTarget: 'umd',
74 | library: 'vue-h-zoom',
75 | umdNamedDefine: true
76 | }
77 | })
78 | ]
79 |
--------------------------------------------------------------------------------
/dist/vue-h-zoom.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("vue-h-zoom",[],e):"object"==typeof exports?exports["vue-h-zoom"]=e():t["vue-h-zoom"]=e()}(this,function(){return function(t){function e(n){if(o[n])return o[n].exports;var i=o[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var o={};return e.m=t,e.c=o,e.i=function(t){return t},e.d=function(t,o,n){e.o(t,o)||Object.defineProperty(t,o,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var o=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(o,"a",o),o},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=6)}([function(t,e,o){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={name:"vue-h-zoom",props:{image:{type:String,required:!0},imageFull:{type:String,default:""},width:{type:Number,default:200},height:{type:Number,default:200},zoomLevel:{type:Number,default:4},zoomWindowSize:{type:Number,default:2},zoomWindowX:{type:Number,default:300},zoomWindowY:{type:Number,default:10}},data:function(){return{visibleZoom:!1,pointer:{x:0,y:0},thumbnailPos:{}}},methods:{toPx:function(t){return t+"px"},mouseEnter:function(){this.updateThumbnailPos(),this.visibleZoom=!0},mouseLeave:function(){this.visibleZoom=!1},followMouse:function(t){this.pointer={x:t.pageX,y:t.pageY}},updateThumbnailPos:function(){var t=this.$refs.thumbnail;this.thumbnailPos={top:t.offsetTop,left:t.offsetLeft}}},computed:{zoomWidth:function(){return this.zoomWindowSize*this.width},zoomHeight:function(){return this.zoomWindowSize*this.height},thumbnailStyle:function(){return{"background-image":"url("+this.image+")","background-size":"cover",height:this.toPx(this.height),width:this.toPx(this.width)}},containerStyle:function(){return{height:this.toPx(this.zoomHeight),width:this.toPx(this.zoomWidth),left:this.toPx(this.zoomWindowX),top:this.toPx(this.zoomWindowY),position:"absolute",overflow:"hidden",border:"1px solid #ccc"}},zoomPosX:function(){var t=this.width/2,e=-(this.pointer.x-this.thumbnailPos.left-t)*this.zoomWindowSize;return e>this.pointerEdgeX?this.pointerEdgeX:e<-1*this.pointerEdgeX?-1*this.pointerEdgeX:e},zoomPosY:function(){var t=this.height/2,e=-(this.pointer.y-this.thumbnailPos.top-t)*this.zoomWindowSize;return e>this.pointerEdgeY?this.pointerEdgeY:e<-1*this.pointerEdgeY?-1*this.pointerEdgeY:e},zoomStyle:function(){return{"background-image":"url("+this.largeImage+")","background-repeat":"no-repeat","background-position":this.toPx(this.zoomPosX)+" "+this.toPx(this.zoomPosY),"background-size":"cover",width:"100%",height:"100%","-webkit-transform":"scale("+this.zoomLevel+")",transform:"scale("+this.zoomLevel+")"}},pointerWidth:function(){return this.width/this.zoomLevel},pointerHeight:function(){return this.height/this.zoomLevel},pointerOffsetTop:function(){var t=this.pointer.y-this.pointerHeight/2-this.thumbnailPos.top;return t<0?0:t>this.height-this.pointerHeight?this.height-this.pointerHeight:t},pointerOffsetLeft:function(){var t=this.pointer.x-this.pointerWidth/2-this.thumbnailPos.left;return t<0?0:t>this.width-this.pointerWidth?this.width-this.pointerWidth:t},pointerEdgeX:function(){return(this.width-this.pointerWidth)*(this.zoomWindowSize/2)},pointerEdgeY:function(){return(this.height-this.pointerHeight)*(this.zoomWindowSize/2)},pointerBoxStyle:function(){return{position:"absolute","z-index":"999",transform:"translateZ(0px)",top:this.toPx(this.pointerOffsetTop),left:this.toPx(this.pointerOffsetLeft),width:this.toPx(this.pointerWidth),height:this.toPx(this.pointerHeight),background:"gray",opacity:.5,border:"1px solid white",cursor:"crosshair"}},largeImage:function(){return this.imageFull||this.image}}}},function(t,e){t.exports=function(t,e,o,n,i,r){var s,u=t=t||{},a=typeof t.default;"object"!==a&&"function"!==a||(s=t,u=t.default);var h="function"==typeof u?u.options:u;e&&(h.render=e.render,h.staticRenderFns=e.staticRenderFns,h._compiled=!0),o&&(h.functional=!0),i&&(h._scopeId=i);var d;if(r?(d=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),n&&n.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(r)},h._ssrRegister=d):n&&(d=n),d){var f=h.functional,c=f?h.render:h.beforeCreate;f?(h._injectStyles=d,h.render=function(t,e){return d.call(e),c(t,e)}):h.beforeCreate=c?[].concat(c,d):[d]}return{esModule:s,exports:u,options:h}}},function(t,e,o){"use strict";var n=function(){var t=this,e=t.$createElement,o=t._self._c||e;return o("div",{staticClass:"vue-h-zoom"},[o("div",{ref:"thumbnail",staticClass:"thumbnail-area",style:t.thumbnailStyle,on:{mouseenter:t.mouseEnter,mouseleave:t.mouseLeave,mousemove:function(e){if(e.target!==e.currentTarget)return null;t.followMouse(e)}}},[t.visibleZoom?o("div",{style:t.pointerBoxStyle,on:{mouseenter:t.mouseEnter,mousemove:function(e){if(e.target!==e.currentTarget)return null;t.followMouse(e)}}}):t._e()]),t._v(" "),t.visibleZoom?o("div",{staticClass:"img-zoom-container",style:t.containerStyle},[o("div",{style:t.zoomStyle})]):t._e()])},i=[];n._withStripped=!0;var r={render:n,staticRenderFns:i};e.a=r},function(t,e,o){var n=o(4);"string"==typeof n&&(n=[[t.i,n,""]]),n.locals&&(t.exports=n.locals);o(7)("7aca82c2",n,!1,{})},function(t,e,o){e=t.exports=o(5)(!1),e.push([t.i,"\n.thumbnail-area[data-v-cd0bf226] {\n overflow: hidden;\n position: relative;\n}\n.img-zoom-container[data-v-cd0bf226] {\n -webkit-box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.3);\n -moz-box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.3);\n box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.3);\n z-index: 999;\n}\n",""])},function(t,e){function o(t,e){var o=t[1]||"",i=t[3];if(!i)return o;if(e&&"function"==typeof btoa){var r=n(i);return[o].concat(i.sources.map(function(t){return"/*# sourceURL="+i.sourceRoot+t+" */"})).concat([r]).join("\n")}return[o].join("\n")}function n(t){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(t))))+" */"}t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var n=o(e,t);return e[2]?"@media "+e[2]+"{"+n+"}":n}).join("")},e.i=function(t,o){"string"==typeof t&&(t=[[null,t,""]]);for(var n={},i=0;io.parts.length&&(n.parts.length=o.parts.length)}else{for(var s=[],i=0;ithis.pointerEdgeX?this.pointerEdgeX:e<-1*this.pointerEdgeX?-1*this.pointerEdgeX:e},zoomPosY:function(){var t=this.height/2,e=-(this.pointer.y-this.thumbnailPos.top-t)*this.zoomWindowSize;return e>this.pointerEdgeY?this.pointerEdgeY:e<-1*this.pointerEdgeY?-1*this.pointerEdgeY:e},zoomStyle:function(){return{"background-image":"url("+this.largeImage+")","background-repeat":"no-repeat","background-position":this.toPx(this.zoomPosX)+" "+this.toPx(this.zoomPosY),"background-size":"cover",width:"100%",height:"100%","-webkit-transform":"scale("+this.zoomLevel+")",transform:"scale("+this.zoomLevel+")"}},pointerWidth:function(){return this.width/this.zoomLevel},pointerHeight:function(){return this.height/this.zoomLevel},pointerOffsetTop:function(){var t=this.pointer.y-this.pointerHeight/2-this.thumbnailPos.top;return t<0?0:t>this.height-this.pointerHeight?this.height-this.pointerHeight:t},pointerOffsetLeft:function(){var t=this.pointer.x-this.pointerWidth/2-this.thumbnailPos.left;return t<0?0:t>this.width-this.pointerWidth?this.width-this.pointerWidth:t},pointerEdgeX:function(){return(this.width-this.pointerWidth)*(this.zoomWindowSize/2)},pointerEdgeY:function(){return(this.height-this.pointerHeight)*(this.zoomWindowSize/2)},pointerBoxStyle:function(){return{position:"absolute","z-index":"999",transform:"translateZ(0px)",top:this.toPx(this.pointerOffsetTop),left:this.toPx(this.pointerOffsetLeft),width:this.toPx(this.pointerWidth),height:this.toPx(this.pointerHeight),background:"gray",opacity:.5,border:"1px solid white",cursor:"crosshair"}},largeImage:function(){return this.imageFull||this.image}}}},function(t,e,n){"use strict";function o(t){a||n(7)}Object.defineProperty(e,"__esModule",{value:!0});var i=n(0),r=n.n(i);for(var s in i)["default","default"].indexOf(s)<0&&function(t){n.d(e,t,function(){return i[t]})}(s);var u=n(6),a=!1,h=n(5),d=o,f=h(r.a,u.a,!1,d,"data-v-cd0bf226",null);f.options.__file="src/libs/VueHZoom.vue",e.default=f.exports},function(t,e,n){"use strict";var o=n(1),i=function(t){return t&&t.__esModule?t:{default:t}}(o);t.exports={install:function(t,e){t.component("vue-h-zoom",i.default)}}},function(t,e,n){e=t.exports=n(4)(!1),e.push([t.i,"\n.thumbnail-area[data-v-cd0bf226] {\n overflow: hidden;\n position: relative;\n}\n.img-zoom-container[data-v-cd0bf226] {\n -webkit-box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.3);\n -moz-box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.3);\n box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.3);\n z-index: 999;\n}\n",""])},function(t,e){function n(t,e){var n=t[1]||"",i=t[3];if(!i)return n;if(e&&"function"==typeof btoa){var r=o(i);return[n].concat(i.sources.map(function(t){return"/*# sourceURL="+i.sourceRoot+t+" */"})).concat([r]).join("\n")}return[n].join("\n")}function o(t){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(t))))+" */"}t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var o=n(e,t);return e[2]?"@media "+e[2]+"{"+o+"}":o}).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var o={},i=0;in.parts.length&&(o.parts.length=n.parts.length)}else{for(var s=[],i=0;i= 6.0"
80 | },
81 | "repository": {
82 | "type": "git",
83 | "url": "git+https://github.com/bliblidotcom/vue-h-zoom.git"
84 | },
85 | "scripts": {
86 | "clean": "rm -rf coverage && rm -rf dist/*.js* && rm ./*.log",
87 | "dev": "cross-env BABEL_ENV=test webpack-dev-server --inline --hot --open --content-base ./test/unit/ --config config/webpack.dev.conf.js",
88 | "start": "npm run dev",
89 | "lint": "eslint src test config",
90 | "test": "npm run lint && npm run flow && npm run test:cover && npm run test:e2e",
91 | "test:cover": "cross-env BABEL_ENV=test karma start config/karma.cover.conf.js",
92 | "test:cover-upload": "npm run test:cover && codecov",
93 | "test:unit": "cross-env BABEL_ENV=test karma start config/karma.unit.conf.js",
94 | "build": "rm -rf dist/*.js* && webpack --config config/webpack.prod.conf.js",
95 | "prepublish": "npm run build"
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/assets/bugatti-chiron-white_01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bliblidotcom/vue-h-zoom/ecc385f89bc707a814e51ce951aa16b58bebe588/src/assets/bugatti-chiron-white_01.jpg
--------------------------------------------------------------------------------
/src/assets/bugatti-chiron-white_01_thumb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bliblidotcom/vue-h-zoom/ecc385f89bc707a814e51ce951aa16b58bebe588/src/assets/bugatti-chiron-white_01_thumb.jpg
--------------------------------------------------------------------------------
/src/demo.js:
--------------------------------------------------------------------------------
1 | // /** Lines below are already loaded in /test/helpers/entry.js
2 | // import Vue from 'vue'
3 | // import plugin from './index'
4 | // import 'babel-polyfill' // promise and etc ...
5 | //
6 | // Vue.config.productionTip = false
7 | // Vue.use(plugin)
8 | //
9 | // window.Vue = Vue
10 | // Vue.config.debug = true
11 | // */
12 |
13 | import VueHZoom from './libs/VueHZoom.vue'
14 |
15 | new window.Vue({
16 | el: 'app',
17 | template: ``,
20 | components: { VueHZoom }
21 | })
22 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import plugin from './libs/VueHZoom.vue'
3 |
4 | module.exports = {
5 | install: function (Vue, options) {
6 | Vue.component('vue-h-zoom', plugin)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/libs/VueHZoom.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/libs/vue-h-zoom.js:
--------------------------------------------------------------------------------
1 | const DEFAULT_BACKGROUND_OPTIONS = {
2 | image: 'none',
3 | color: '#fff',
4 | repeat: false,
5 | size: '100%',
6 | position: 'top left'
7 | }
8 |
9 | export default {
10 | name: 'vue-h-zoom',
11 | props: {
12 | image: {
13 | type: String,
14 | required: true
15 | },
16 | imageFull: {
17 | type: String,
18 | default: ''
19 | },
20 | width: {
21 | type: Number,
22 | default: 200
23 | },
24 | height: {
25 | type: Number,
26 | default: 200
27 | },
28 | zoomLevel: {
29 | type: Number,
30 | default: 4
31 | },
32 | zoomWindowSize: {
33 | type: Number,
34 | default: 2
35 | },
36 | zoomWindowX: {
37 | type: Number,
38 | default: 300
39 | },
40 | zoomWindowY: {
41 | type: Number,
42 | default: 10
43 | },
44 | backgroundOptions: {
45 | type: Object,
46 | default: null
47 | }
48 | },
49 | data () {
50 | return {
51 | visibleZoom: false,
52 | pointer: {
53 | x: 0,
54 | y: 0
55 | },
56 | thumbnailPos: {}
57 | }
58 | },
59 | methods: {
60 | toPx: function (v) {
61 | return v + 'px'
62 | },
63 | mouseEnter: function () {
64 | this.updateThumbnailPos()
65 | this.visibleZoom = true
66 | },
67 | mouseLeave: function () {
68 | this.visibleZoom = false
69 | },
70 | followMouse: function (e) {
71 | this.pointer = {
72 | x: e.pageX - this.$refs.thumbnail.getBoundingClientRect().left - window.scrollX,
73 | y: e.pageY - this.$refs.thumbnail.getBoundingClientRect().top - window.scrollY
74 | }
75 | },
76 | updateThumbnailPos: function () {
77 | const el = this.$refs.thumbnail
78 | this.thumbnailPos = {
79 | top: el.offsetTop,
80 | left: el.offsetLeft
81 | }
82 | }
83 | },
84 | computed: {
85 | zoomWidth: function () {
86 | return this.zoomWindowSize * this.width
87 | },
88 | zoomHeight: function () {
89 | return this.zoomWindowSize * this.height
90 | },
91 | mainImgStyle: function () {
92 | return {
93 | contain: this.backgroundOptions ? 'contain' : 'cover',
94 | repeat: 'no-repeat',
95 | position: '50% 50%'
96 | }
97 | },
98 | customBackgroundOptions: function () {
99 | return this.backgroundOptions ? this.backgroundOptions : DEFAULT_BACKGROUND_OPTIONS
100 | },
101 | customBackgroundStyle: function () {
102 | return {
103 | image: this.customBackgroundOptions.image || DEFAULT_BACKGROUND_OPTIONS.image,
104 | repeat: this.customBackgroundOptions.repeat ? 'repeat' : 'no-repeat',
105 | color: this.customBackgroundOptions.color || DEFAULT_BACKGROUND_OPTIONS.color,
106 | size: this.customBackgroundOptions.size || DEFAULT_BACKGROUND_OPTIONS.size,
107 | position: this.customBackgroundOptions.position || DEFAULT_BACKGROUND_OPTIONS.position
108 | }
109 | },
110 | thumbnailStyle: function () {
111 | return {
112 | 'background-image': `url("${this.image}"), url("${this.customBackgroundStyle.image}")`,
113 | 'background-size': `${this.mainImgStyle.contain}, ${this.customBackgroundStyle.size}`,
114 | 'background-repeat': `${this.mainImgStyle.repeat}, ${this.customBackgroundStyle.repeat}`,
115 | 'background-position': `${this.mainImgStyle.position}, ${this.customBackgroundStyle.position}`,
116 | 'background-color': this.customBackgroundStyle.color,
117 | height: this.toPx(this.height),
118 | width: this.toPx(this.width)
119 | }
120 | },
121 | containerStyle: function () {
122 | return {
123 | height: this.toPx(this.zoomHeight),
124 | width: this.toPx(this.zoomWidth),
125 | left: this.toPx(this.zoomWindowX),
126 | top: this.toPx(this.zoomWindowY),
127 | position: 'absolute',
128 | overflow: 'hidden',
129 | border: '1px solid #ccc'
130 | }
131 | },
132 | zoomPosX: function () {
133 | const xPad = this.width / 2
134 | const posX = -(this.pointer.x - this.thumbnailPos.left - xPad) * this.zoomWindowSize
135 | if (posX > this.pointerEdgeX) return this.pointerEdgeX
136 | if (posX < (this.pointerEdgeX * -1)) return (this.pointerEdgeX * -1)
137 | return posX
138 | },
139 | zoomPosY: function () {
140 | const yPad = (this.height / 2)
141 | const posY = -(this.pointer.y - this.thumbnailPos.top - yPad) * this.zoomWindowSize
142 | if (posY > this.pointerEdgeY) return this.pointerEdgeY
143 | if (posY < (this.pointerEdgeY * -1)) return (this.pointerEdgeY * -1)
144 | return posY
145 | },
146 | zoomStyle: function () {
147 | return {
148 | 'background-image': `url("${this.largeImage}"), url("${this.customBackgroundStyle.image}")`,
149 | 'background-size': `${this.mainImgStyle.contain}, ${this.customBackgroundStyle.size}`,
150 | 'background-repeat': `${this.mainImgStyle.repeat}, ${this.customBackgroundStyle.repeat}`,
151 | 'background-position': `${this.mainImgStyle.position}, ${this.customBackgroundStyle.position}`,
152 | 'background-color': this.customBackgroundStyle.color,
153 | width: '100%',
154 | height: '100%',
155 | '-webkit-transform': `scale(${this.zoomLevel})`,
156 | transform: `
157 | scale(${this.zoomLevel})
158 | translate(${this.toPx(this.zoomPosX)}, ${this.toPx(this.zoomPosY)})
159 | `
160 | }
161 | },
162 | pointerWidth: function () {
163 | return this.width / this.zoomLevel
164 | },
165 | pointerHeight: function () {
166 | return this.height / this.zoomLevel
167 | },
168 | pointerOffsetTop: function () {
169 | const top = this.pointer.y - (this.pointerHeight / 2) - this.thumbnailPos.top
170 | if (top < 0) return 0
171 | if (top > (this.height - this.pointerHeight)) return (this.height - this.pointerHeight)
172 | return top
173 | },
174 | pointerOffsetLeft: function () {
175 | const left = this.pointer.x - (this.pointerWidth / 2) - this.thumbnailPos.left
176 | if (left < 0) return 0
177 | if (left > (this.width - this.pointerWidth)) return (this.width - this.pointerWidth)
178 | return left
179 | },
180 | pointerEdgeX: function () {
181 | // you have to bound it within the reduced with from pointerwidth multiplied by half zoom window size
182 | return (this.width - this.pointerWidth) * (this.zoomWindowSize / 2)
183 | },
184 | pointerEdgeY: function () {
185 | return (this.height - this.pointerHeight) * (this.zoomWindowSize / 2)
186 | },
187 | pointerBoxStyle: function () {
188 | return {
189 | position: 'absolute',
190 | 'z-index': '2',
191 | transform: 'translateZ(0px)',
192 | top: this.toPx(this.pointerOffsetTop),
193 | left: this.toPx(this.pointerOffsetLeft),
194 | width: this.toPx(this.pointerWidth),
195 | height: this.toPx(this.pointerHeight),
196 | background: 'gray',
197 | opacity: 0.5,
198 | border: '1px solid white',
199 | cursor: 'crosshair'
200 | }
201 | },
202 | largeImage: function () {
203 | return this.imageFull || this.image
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "mocha": true
5 | },
6 | "globals": {
7 | "waitForUpdate": true,
8 | "nextTick": true,
9 | "delay": true,
10 | "assert": true,
11 | "Vue": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const spawn = require('cross-spawn')
3 | const httpServer = require('http-server')
4 | const server = httpServer.createServer({
5 | root: path.resolve(__dirname, '../../')
6 | })
7 |
8 | server.listen(8080)
9 |
10 | let args = process.argv.slice(2)
11 | if (args.indexOf('--config') === -1) {
12 | args = args.concat(['--config', 'config/nightwatch.conf.js'])
13 | }
14 | if (args.indexOf('--env') === -1) {
15 | args = args.concat(['--env', 'chrome,headless'])
16 | }
17 | const i = args.indexOf('--test')
18 | if (i > -1) {
19 | args[i + 1] = 'test/e2e/test/' + args[i + 1]
20 | }
21 |
22 | const runner = spawn('./node_modules/.bin/nightwatch', args, {
23 | stdio: 'inherit'
24 | })
25 |
26 | runner.on('exit', code => {
27 | server.close()
28 | process.exit(code)
29 | })
30 |
31 | runner.on('error', err => {
32 | server.close()
33 | throw err
34 | })
35 |
--------------------------------------------------------------------------------
/test/e2e/test/add.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | add: function (browser) {
3 | browser
4 | .url('http://localhost:8080/examples/add/')
5 | .waitForElementVisible('p', 1000)
6 | .assert.containsText('p', '2', 'You should be implemented !!')
7 | .end()
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/helpers/assert.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 |
3 | window.assert = assert
4 |
--------------------------------------------------------------------------------
/test/helpers/entry.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import plugin from '../../src/index'
3 | import 'babel-polyfill' // promise and etc ...
4 |
5 | Vue.config.productionTip = false
6 | Vue.use(plugin)
7 |
8 | window.Vue = Vue
9 | Vue.config.debug = true
10 |
--------------------------------------------------------------------------------
/test/helpers/wait-for-update.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | // helper for jasmine async assertions.
4 | //
5 | // Use like this:
6 | //
7 | // vm.a = 123
8 | // waitForUpdate(() => {
9 | // expect(vm.$el.textContent).toBe('123')
10 | // vm.a = 234
11 | // })
12 | // .then(() => {
13 | // // more assertions...
14 | // })
15 | // .then(done)
16 | window.waitForUpdate = initialCb => {
17 | const queue = initialCb ? [initialCb] : []
18 |
19 | function shift () {
20 | const job = queue.shift()
21 | if (queue.length) {
22 | let hasError = false
23 | try {
24 | job.wait ? job(shift) : job()
25 | } catch (e) {
26 | hasError = true
27 | const done = queue[queue.length - 1]
28 | if (done && done.fail) {
29 | done.fail(e)
30 | }
31 | }
32 | if (!hasError && !job.wait) {
33 | if (queue.length) {
34 | Vue.nextTick(shift)
35 | }
36 | }
37 | } else if (job && job.fail) {
38 | job() // done
39 | }
40 | }
41 |
42 | Vue.nextTick(() => {
43 | if (!queue.length || !queue[queue.length - 1].fail) {
44 | console.warn('waitForUpdate chain is missing .then(done)')
45 | }
46 | shift()
47 | })
48 |
49 | const chainer = {
50 | then: nextCb => {
51 | queue.push(nextCb)
52 | return chainer
53 | },
54 | thenWaitFor: (wait) => {
55 | if (typeof wait === 'number') {
56 | wait = timeout(wait)
57 | }
58 | wait.wait = true
59 | queue.push(wait)
60 | return chainer
61 | }
62 | }
63 |
64 | return chainer
65 | }
66 |
67 | function timeout (n) {
68 | return next => setTimeout(next, n)
69 | }
70 |
71 | // helper for mocha async assertions.
72 | // nextTick(() => {
73 | //
74 | // Automatically waits for nextTick
75 | // }).then(() => {
76 | // return a promise or value to skip the wait
77 | // })
78 | function nextTick (initialCb) {
79 | const jobs = initialCb ? [initialCb] : []
80 | let done
81 |
82 | const chainer = {
83 | then (cb) {
84 | jobs.push(cb)
85 | return chainer
86 | }
87 | }
88 |
89 | function shift (...args) {
90 | const job = jobs.shift()
91 | let result
92 | try {
93 | result = job(...args)
94 | } catch (e) {
95 | jobs.length = 0
96 | done(e)
97 | }
98 |
99 | // wait for nextTick
100 | if (result !== undefined) {
101 | if (result.then) {
102 | result.then(shift)
103 | } else {
104 | shift(result)
105 | }
106 | } else if (jobs.length) {
107 | requestAnimationFrame(() => Vue.nextTick(shift))
108 | }
109 | }
110 |
111 | // First time
112 | Vue.nextTick(() => {
113 | done = jobs[jobs.length - 1]
114 | if (done.toString().slice(0, 14) !== 'function (err)') {
115 | throw new Error('waitForUpdate chain is missing .then(done)')
116 | }
117 | shift()
118 | })
119 |
120 | return chainer
121 | }
122 |
123 | window.nextTick = nextTick
124 | window.delay = time => new Promise(resolve => setTimeout(resolve, time))
125 |
--------------------------------------------------------------------------------
/test/unit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | vue-h-zoom tests
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | // import all helpers
2 | const helpersContext = require.context('../helpers', true)
3 | helpersContext.keys().forEach(helpersContext)
4 |
5 | // require all test files
6 | const testsContext = require.context('./', true, /\.test/)
7 | testsContext.keys().forEach(testsContext)
8 |
--------------------------------------------------------------------------------
/test/unit/vue-h-zoom.test.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueHZoom from '@/libs/VueHZoom.vue'
3 |
4 | describe('VueHZoom', () => {
5 | let vm
6 |
7 | beforeEach(() => {
8 | vm = new (Vue.extend(VueHZoom))({
9 | propsData: {
10 | image: '/abc.jpg'
11 | }
12 | })
13 | vm.$mount()
14 | })
15 |
16 | describe('image', () => {
17 | it('should be /abc.jpg', () => {
18 | assert(vm.image === '/abc.jpg', 'You should be implemented!!')
19 | })
20 | })
21 |
22 | describe('imageFull', () => {
23 | it('should be \'\'', done => {
24 | nextTick(() => {
25 | assert(vm.imageFull === '', 'You should be implemented!!')
26 | }).then(done)
27 | })
28 |
29 | it('largeImage should be /abc.jpg', done => {
30 | vm = new (Vue.extend(VueHZoom))({
31 | propsData: {
32 | image: '/abc.jpg'
33 | }
34 | })
35 |
36 | nextTick(() => {
37 | assert(vm.largeImage === '/abc.jpg', 'You should be implemented!!')
38 | }).then(done)
39 | })
40 |
41 | it('largeImage should be /abc_full.jpg', done => {
42 | vm = new (Vue.extend(VueHZoom))({
43 | propsData: {
44 | image: '/abc.jpg',
45 | imageFull: '/abc_full.jpg'
46 | }
47 | })
48 |
49 | nextTick(() => {
50 | assert(vm.largeImage === '/abc_full.jpg', 'You should be implemented!!')
51 | }).then(done)
52 | })
53 | })
54 |
55 | describe('toPx', () => {
56 | it('should add px', () => {
57 | assert(vm.toPx(123) === '123px')
58 | })
59 | })
60 |
61 | describe('mouse Enter / leave', () => {
62 | it('enter: zoom should be visible', () => {
63 | vm.visibleZoom = false
64 | vm.mouseEnter()
65 | assert(vm.visibleZoom === true)
66 | })
67 | it('enter: zoom should be not visible', () => {
68 | vm.visibleZoom = true
69 | vm.mouseLeave()
70 | assert(vm.visibleZoom === false)
71 | })
72 | })
73 |
74 | describe('followMouse', () => {
75 | it('should be obtaining event location in page', () => {
76 | const loc = {
77 | pageX: 144,
78 | pageY: 134
79 | }
80 | const expectedLoc = {
81 | x: 144,
82 | y: 134
83 | }
84 | vm.followMouse(loc)
85 |
86 | assert.deepEqual(vm.pointer, expectedLoc)
87 | })
88 | })
89 |
90 | describe('updateThumbnailPos', () => {
91 | it('should be correct', () => {
92 | vm.updateThumbnailPos()
93 |
94 | const el = vm.$refs.thumbnail
95 | const loc = {
96 | top: el.offsetTop,
97 | left: el.offsetLeft
98 | }
99 | assert.deepEqual(vm.thumbnailPos, loc)
100 | })
101 | })
102 |
103 | describe('zoom dimensions', () => {
104 | it('should multiply thumbnail dimension', () => {
105 | assert((vm.zoomWindowSize * vm.width) === vm.zoomWidth)
106 | assert((vm.zoomWindowSize * vm.height) === vm.zoomHeight)
107 | })
108 | })
109 |
110 | describe('mainImgStyle', () => {
111 | it('should have correct values when backgroundOptions is falsy', () => {
112 | const expected = {
113 | contain: 'cover',
114 | repeat: 'no-repeat',
115 | position: '50% 50%'
116 | }
117 | assert.deepEqual(vm.mainImgStyle, expected)
118 | })
119 |
120 | it('should have correct values when backgroundOptions is truthy', done => {
121 | vm = new (Vue.extend(VueHZoom))({
122 | propsData: {
123 | image: '/abc.jpg',
124 | backgroundOptions: true
125 | }
126 | })
127 | const expected = {
128 | contain: 'contain',
129 | repeat: 'no-repeat',
130 | position: '50% 50%'
131 | }
132 | nextTick(() => {
133 | assert.deepEqual(vm.mainImgStyle, expected)
134 | }).then(done)
135 | })
136 | })
137 |
138 | describe('customBackgroundOptions', () => {
139 | it('should have correct values when backgroundOptions is falsy', () => {
140 | const expected = {
141 | image: 'none',
142 | color: '#fff',
143 | repeat: false,
144 | size: '100%',
145 | position: 'top left'
146 | }
147 | assert.deepEqual(vm.customBackgroundOptions, expected)
148 | })
149 |
150 | it('should have correct values when backgroundOptions is truthy', done => {
151 | vm = new (Vue.extend(VueHZoom))({
152 | propsData: {
153 | image: '/abc.jpg',
154 | backgroundOptions: {
155 | color: 'blue'
156 | }
157 | }
158 | })
159 | const expected = {
160 | color: 'blue'
161 | }
162 | nextTick(() => {
163 | assert.deepEqual(vm.customBackgroundOptions, expected)
164 | }).then(done)
165 | })
166 | })
167 |
168 | describe('customBackgroundStyle', () => {
169 | it('should have correct values when backgroundOptions prop is truthy but has no properties', () => {
170 | vm = new (Vue.extend(VueHZoom))({
171 | propsData: {
172 | image: '/abc.jpg',
173 | backgroundOptions: {
174 | image: '/asd.jpg',
175 | repeat: true,
176 | color: 'blue',
177 | size: '50% 100%',
178 | position: 'top left'
179 | }
180 | }
181 | })
182 | const expected = {
183 | image: '/asd.jpg',
184 | repeat: 'repeat',
185 | color: 'blue',
186 | size: '50% 100%',
187 | position: 'top left'
188 | }
189 | assert.deepEqual(vm.customBackgroundStyle, expected)
190 | })
191 |
192 | it('should have correct values when backgroundOptions is truthy', done => {
193 | vm = new (Vue.extend(VueHZoom))({
194 | propsData: {
195 | image: '/abc.jpg',
196 | backgroundOptions: {}
197 | }
198 | })
199 | const expected = {
200 | image: 'none',
201 | color: '#fff',
202 | repeat: 'no-repeat',
203 | size: '100%',
204 | position: 'top left'
205 | }
206 | nextTick(() => {
207 | assert.deepEqual(vm.customBackgroundStyle, expected)
208 | }).then(done)
209 | })
210 | })
211 |
212 | describe('thumbnailStyle', () => {
213 | it('should have correct values', () => {
214 | vm.height = 1000
215 | vm.width = 100
216 | const expected = {
217 | 'background-image': `url("${vm.image}"), url("${vm.customBackgroundStyle.image}")`,
218 | 'background-size': `${vm.mainImgStyle.contain}, ${vm.customBackgroundStyle.size}`,
219 | 'background-repeat': `${vm.mainImgStyle.repeat}, ${vm.customBackgroundStyle.repeat}`,
220 | 'background-position': `${vm.mainImgStyle.position}, ${vm.customBackgroundStyle.position}`,
221 | 'background-color': vm.customBackgroundStyle.color,
222 | height: vm.toPx(1000),
223 | width: vm.toPx(100)
224 | }
225 | assert.deepEqual(vm.thumbnailStyle, expected)
226 | })
227 | })
228 |
229 | describe('containerStyle', () => {
230 | it('should have correct values', () => {
231 | const expected = {
232 | height: vm.toPx(vm.zoomHeight),
233 | width: vm.toPx(vm.zoomWidth),
234 | left: vm.toPx(vm.zoomWindowX),
235 | top: vm.toPx(vm.zoomWindowY),
236 | position: 'absolute',
237 | overflow: 'hidden',
238 | border: '1px solid #ccc'
239 | }
240 | assert.deepEqual(vm.containerStyle, expected)
241 | })
242 | })
243 |
244 | describe('zoomPosX', () => {
245 | it('should calculate correctly', () => {
246 | vm.width = 400
247 | vm.height = 500
248 | vm.pointer = {
249 | x: 200,
250 | y: 300
251 | }
252 | vm.thumbnailPos = {
253 | left: 50,
254 | top: 100
255 | }
256 | vm.zoomLevel = 2
257 | vm.zoomWindowSize = 3
258 | assert.deepEqual(vm.zoomPosX, 150)
259 | })
260 | })
261 |
262 | describe('zoomPosY', () => {
263 | it('should calculate correctly', () => {
264 | vm.width = 400
265 | vm.height = 500
266 | vm.pointer = {
267 | x: 200,
268 | y: 300
269 | }
270 | vm.thumbnailPos = {
271 | left: 50,
272 | top: 100
273 | }
274 | vm.zoomLevel = 2
275 | vm.zoomWindowSize = 3
276 | assert.deepEqual(vm.zoomPosY, 150)
277 | })
278 | })
279 |
280 | describe('pointerWidth', () => {
281 | it('should calculate correctly', () => {
282 | vm.width = 400
283 | vm.zoomLevel = 2
284 | assert.deepEqual(vm.pointerWidth, 200)
285 | })
286 | })
287 |
288 | describe('pointerHeight', () => {
289 | it('should calculate correctly', () => {
290 | vm.height = 100
291 | vm.zoomLevel = 2
292 | assert.deepEqual(vm.pointerHeight, 50)
293 | })
294 | })
295 |
296 | describe('pointerOffsetTop', () => {
297 | it('should calculate correctly', () => {
298 | vm.height = 100
299 | vm.zoomLevel = 2
300 | vm.pointer = {
301 | x: 200,
302 | y: 300
303 | }
304 | vm.thumbnailPos = {
305 | left: 50,
306 | top: 100
307 | }
308 | assert.deepEqual(vm.pointerOffsetTop, 50)
309 | })
310 | })
311 |
312 | describe('pointerOffsetLeft', () => {
313 | it('should calculate correctly', () => {
314 | vm.height = 100
315 | vm.zoomLevel = 2
316 | vm.pointer = {
317 | x: 200,
318 | y: 300
319 | }
320 | vm.thumbnailPos = {
321 | left: 50,
322 | top: 100
323 | }
324 | assert.deepEqual(vm.pointerOffsetLeft, 100)
325 | })
326 | })
327 |
328 | describe('pointerEdgeX', () => {
329 | it('should calculate correctly', () => {
330 | vm.width = 400
331 | vm.zoomLevel = 2
332 | assert.deepEqual(vm.pointerEdgeX, 200)
333 | })
334 | })
335 |
336 | describe('pointerEdgeY', () => {
337 | it('should calculate correctly', () => {
338 | vm.height = 400
339 | vm.zoomLevel = 2
340 | assert.deepEqual(vm.pointerEdgeY, 200)
341 | })
342 | })
343 |
344 | describe('zoomStyle', () => {
345 | it('should calculate correctly', () => {
346 | vm.width = 400
347 | vm.height = 500
348 | vm.pointer = {
349 | x: 200,
350 | y: 300
351 | }
352 | vm.thumbnailPos = {
353 | left: 50,
354 | top: 100
355 | }
356 | vm.zoomWindowSize = 3
357 | // the position of mouse, relative to the element, and multiply by window size, because it will be reflected
358 | // in zoom window
359 | const posX = -(200 - 50 - 200) * 3
360 | const posY = -(300 - 100 - 250) * 3
361 |
362 | const expected = {
363 | 'background-image': `url("${vm.largeImage}"), url("${vm.customBackgroundStyle.image}")`,
364 | 'background-size': `${vm.mainImgStyle.contain}, ${vm.customBackgroundStyle.size}`,
365 | 'background-repeat': `${vm.mainImgStyle.repeat}, ${vm.customBackgroundStyle.repeat}`,
366 | 'background-position': `${vm.mainImgStyle.position}, ${vm.customBackgroundStyle.position}`,
367 | 'background-color': vm.customBackgroundStyle.color,
368 | width: '100%',
369 | height: '100%',
370 | '-webkit-transform': `scale(${vm.zoomLevel})`,
371 | transform: `
372 | scale(${vm.zoomLevel})
373 | translate(${vm.toPx(posX)}, ${vm.toPx(posY)})
374 | `
375 | }
376 |
377 | assert.deepEqual(vm.zoomStyle, expected)
378 | })
379 | })
380 |
381 | describe('pointerBoxStyle', () => {
382 | it('should calculate correctly', () => {
383 | vm.width = 600
384 | vm.height = 800
385 | vm.zoomLevel = 8
386 | const width = vm.width / vm.zoomLevel
387 | const height = vm.height / vm.zoomLevel
388 |
389 | // set zoom lense offset
390 | vm.pointer = {
391 | x: 200,
392 | y: 300
393 | }
394 | vm.thumbnailPos = {
395 | left: 50,
396 | top: 100
397 | }
398 |
399 | // position after padding the margin
400 | const top = 300 - (height / 2) - 100
401 | const left = 200 - (width / 2) - 50
402 | const expected = {
403 | position: 'absolute',
404 | 'z-index': '2',
405 | transform: 'translateZ(0px)',
406 | top: vm.toPx(top),
407 | left: vm.toPx(left),
408 | width: vm.toPx(width),
409 | height: vm.toPx(height),
410 | background: 'gray',
411 | opacity: 0.5,
412 | border: '1px solid white',
413 | cursor: 'crosshair'
414 | }
415 |
416 | assert.deepEqual(vm.pointerBoxStyle, expected)
417 | })
418 | })
419 | })
420 |
421 |
--------------------------------------------------------------------------------