├── .browserslistrc ├── .npmignore ├── docs ├── favicon.ico ├── img │ └── logo.4bd586bc.png ├── index.html ├── css │ └── app.88db7626.css └── js │ ├── app.a9a5325e.js │ └── app.a9a5325e.js.map ├── public ├── favicon.ico └── index.html ├── src ├── demo │ ├── assets │ │ ├── logo.png │ │ └── unimelb.jpg │ ├── main.js │ ├── components │ │ ├── ShowCode.vue │ │ └── Toggle.vue │ └── Demo.vue └── components │ ├── utils │ └── common.js │ ├── module │ ├── ControlBox.vue │ └── DataPoint.vue │ └── VueHotspot.vue ├── .editorconfig ├── babel.config.js ├── .commitlintrc.js ├── tests └── unit │ ├── .eslintrc.js │ ├── common.spec.js │ ├── DataPoint.spec.js │ └── ControlBox.spec.js ├── postcss.config.js ├── publish.sh ├── .gitignore ├── vue.config.js ├── .eslintrc.js ├── .circleci └── config.yml ├── jest.config.js ├── rollup.config.js ├── LICENSE ├── CHANGELOG.md ├── package.json ├── README.zh-CN.md ├── README.md └── dist └── vue-hotspot.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.sh 3 | *.config.js 4 | docs/ 5 | node_modules/ 6 | src/ 7 | public/ -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesxwang/vue-hotspot/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesxwang/vue-hotspot/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/demo/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesxwang/vue-hotspot/HEAD/src/demo/assets/logo.png -------------------------------------------------------------------------------- /docs/img/logo.4bd586bc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesxwang/vue-hotspot/HEAD/docs/img/logo.4bd586bc.png -------------------------------------------------------------------------------- /src/demo/assets/unimelb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesxwang/vue-hotspot/HEAD/src/demo/assets/unimelb.jpg -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file babel.config.js 3 | * @author James Wang 4 | */ 5 | 6 | module.exports = { 7 | presets: ['@vue/app'] 8 | } 9 | -------------------------------------------------------------------------------- /src/demo/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Demo from './Demo.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(Demo) 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file .commitlintrc.js 3 | * @author James Wang 4 | */ 5 | 6 | module.exports = { 7 | extends: ['@commitlint/config-conventional'] 8 | } 9 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file .eslintrc.js 3 | * @author James Wang 4 | */ 5 | 6 | module.exports = { 7 | env: { 8 | jest: true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file postcss.config.js 3 | * @author James Wang 4 | */ 5 | 6 | module.exports = { 7 | plugins: { 8 | autoprefixer: {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | # npm patch 2 | npm version patch 3 | 4 | # build demo & docs 5 | npm run demo 6 | npm run build 7 | 8 | # publish to npmjs.com 9 | cp -r ./src/components . 10 | npm publish 11 | rm -rf ./components -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OS file 3 | .DS_Store 4 | 5 | # node_modules 6 | node_modules/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file vue.config.js 3 | * @author James Wang 4 | */ 5 | 6 | module.exports = { 7 | publicPath: process.env.NODE_ENV === 'production' ? './' : '/', 8 | outputDir: 'docs', 9 | chainWebpack: config => { 10 | config 11 | .entry('app') 12 | .clear() 13 | .add('./src/demo/main.js') 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file .eslintrc.js 3 | * @author James Wang 4 | */ 5 | 6 | module.exports = { 7 | root: true, 8 | 9 | env: { 10 | node: true 11 | }, 12 | 13 | extends: ['plugin:vue/essential', '@vue/standard'], 14 | 15 | rules: { 16 | 'no-console': 'off', 17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 18 | }, 19 | 20 | parserOptions: { 21 | parser: 'babel-eslint' 22 | }, 23 | 24 | overrides: [ 25 | { 26 | files: [ 27 | '**/__tests__/*.{j,t}s?(x)' 28 | ], 29 | env: { 30 | jest: true 31 | } 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | vue-hotspot 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | codecov: codecov/codecov@1.0.2 4 | jobs: 5 | build: 6 | docker: 7 | - image: circleci/node:lts 8 | steps: 9 | - checkout 10 | - run: 11 | name: remove node_modules 12 | command: rm -rf node_modules/ 13 | # - run: 14 | # name: remove package-lock 15 | # command: rm package-lock.json 16 | - run: 17 | name: install dependences 18 | command: npm install 19 | - run: 20 | name: check lint 21 | command: npm run lint 22 | - run: 23 | name: unit test 24 | command: npm run test:unit 25 | - codecov/upload: 26 | file: coverage/*.json 27 | flags: unit-test 28 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | vue-hotspot
-------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file jest.config.js 3 | * @author James Wang 4 | */ 5 | 6 | module.exports = { 7 | moduleFileExtensions: [ 8 | 'js', 9 | 'jsx', 10 | 'json', 11 | 'vue' 12 | ], 13 | transform: { 14 | '^.+\\.vue$': 'vue-jest', 15 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 16 | '^.+\\.jsx?$': 'babel-jest' 17 | }, 18 | transformIgnorePatterns: [ 19 | '/node_modules/' 20 | ], 21 | moduleNameMapper: { 22 | '^@/(.*)$': '/src/$1' 23 | }, 24 | snapshotSerializers: [ 25 | 'jest-serializer-vue' 26 | ], 27 | testMatch: [ 28 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 29 | ], 30 | testURL: 'http://localhost/', 31 | watchPlugins: [ 32 | 'jest-watch-typeahead/filename', 33 | 'jest-watch-typeahead/testname' 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /tests/unit/common.spec.js: -------------------------------------------------------------------------------- 1 | const utils = require('@/components/utils/common.js') 2 | jest.useFakeTimers() 3 | 4 | describe('utils/common.js', () => { 5 | let func 6 | 7 | beforeEach(() => { 8 | func = jest.fn() 9 | }) 10 | 11 | it('throttle execute just once', () => { 12 | const throttle = utils.throttle 13 | const throttledFunc = throttle(func, 1000) 14 | 15 | for (let i = 0; i < 100; i++) { 16 | throttledFunc() 17 | } 18 | 19 | // fast-forward time 20 | jest.runAllTimers() 21 | 22 | expect(func).toBeCalledTimes(1) 23 | }) 24 | 25 | it('debounce execute just once', () => { 26 | const debounce = utils.debounce 27 | const debouncedFunc = debounce(func, 1000) 28 | 29 | for (let i = 0; i < 100; i++) { 30 | debouncedFunc() 31 | } 32 | 33 | // fast-forward time 34 | jest.runAllTimers() 35 | 36 | expect(func).toBeCalledTimes(1) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file rollup.config.js 3 | * @author James Wang 4 | */ 5 | 6 | const vue = require('rollup-plugin-vue') 7 | const buble = require('rollup-plugin-buble') 8 | const { terser } = require('rollup-plugin-terser') 9 | const resolve = require('rollup-plugin-node-resolve') 10 | const commonjs = require('rollup-plugin-commonjs') 11 | 12 | export default { 13 | input: 'src/components/VueHotspot.vue', 14 | output: { 15 | file: 'dist/vue-hotspot.js', 16 | name: 'VueHotspot', 17 | format: 'umd', 18 | globals: { 19 | 'vue': 'Vue', 20 | '@vue/composition-api': 'VueCompositionApi' 21 | } 22 | }, 23 | external: [ 24 | 'vue', 25 | '@vue/composition-api' 26 | ], 27 | plugins: [ 28 | resolve(), 29 | commonjs(), 30 | vue({ 31 | compileTemplate: true, 32 | css: true 33 | }), 34 | buble({ 35 | objectAssign: 'Object.assign' 36 | }), 37 | terser({ 38 | compress: { 39 | global_defs: { 40 | __DEV__: process.env.NODE_ENV !== 'production' 41 | } 42 | } 43 | }) 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /tests/unit/DataPoint.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from '@vue/test-utils' 2 | import DataPoint from '@/components/module/DataPoint.vue' 3 | import VueCompositionApi from '@vue/composition-api' 4 | 5 | describe('DataPoint.vue', () => { 6 | const localVue = createLocalVue() 7 | localVue.use(VueCompositionApi) 8 | 9 | const propsHotspot = { Message: 'test', Title: 'test', x: 12, y: 34 } 10 | const propsConfig = { 11 | hotspotColor: 'blue', 12 | interactivity: 'hover', 13 | textColor: 'black', 14 | messageBoxColor: 'white', 15 | opacity: 0.5 16 | } 17 | let wrapper, vm 18 | 19 | const image = new Image() 20 | const div = document.createElement('div') 21 | 22 | beforeEach(() => { 23 | wrapper = mount(DataPoint, { 24 | localVue, 25 | propsData: { 26 | hotspot: propsHotspot, 27 | config: propsConfig, 28 | imageLoaded: true, 29 | vueHotspotBackgroundImage: image, 30 | vueHotspot: div 31 | }, 32 | attachToDocument: true 33 | }) 34 | vm = wrapper.vm 35 | }) 36 | 37 | it('test', () => { 38 | console.log('help wanted', vm) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 James Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.0.3 2 | * Fix broken links. 3 | 4 | 2.0.2 5 | * Allow customize overlay's text message (#92). 6 | * Update Demo's image source (#93). 7 | 8 | 2.0.1 9 | * Fix background image style (#81) 10 | 11 | 2.0.0 12 | * Add Vue Composition-API (https://github.com/vuejs/composition-api) 13 | 14 | 1.1.8 15 | * Refactor code with composition-api (#68) 16 | * Performance enhancement for window resize listener 17 | 18 | 1.1.7 19 | * Update usage in README. 20 | 21 | 1.1.6 22 | * Fix repopulate hotspots (#60)(#63). 23 | * Add custom color configuration. 24 | 25 | 1.1.5 26 | * Add vue-hotspot logo. 27 | 28 | 1.1.4 29 | * Change DOM.querySelector to Vue.$refs. 30 | 31 | 1.1.3 32 | * Fix object deep copy bug. 33 | 34 | 1.1.2 35 | * Update test cases again. 36 | 37 | 1.1.0 38 | * Update test cases. 39 | 40 | 1.0.3 41 | * Update button area style. 42 | 43 | 1.0.1 44 | * Add Jest unit test & Code Coverage. 45 | * Update Readme. 46 | 47 | 1.0.0 48 | * Add save & remove functions. 49 | 50 | 0.1.3 51 | * Fix mobile display bug. 52 | 53 | 0.1.2 54 | * Enable hotspot interactivity. 55 | * Enable multiple hotspots. 56 | * Fix known bugs. 57 | 58 | 0.1.1 59 | * Fix usage in README. 60 | 61 | 0.1.0 62 | * First version. -------------------------------------------------------------------------------- /tests/unit/ControlBox.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from '@vue/test-utils' 2 | import ControlBox from '@/components/module/ControlBox.vue' 3 | import VueCompositionApi from '@vue/composition-api' 4 | 5 | describe('ControlBox.vue', () => { 6 | const localVue = createLocalVue() 7 | localVue.use(VueCompositionApi) 8 | 9 | const propsConfig = { 10 | editable: true, 11 | data: [] 12 | } 13 | let wrapper, vm 14 | 15 | beforeEach(() => { 16 | wrapper = mount(ControlBox, { 17 | localVue, 18 | propsData: { 19 | config: propsConfig 20 | }, 21 | attachToDocument: true 22 | }) 23 | vm = wrapper.vm 24 | }) 25 | 26 | it('save data function', () => { 27 | const saveButton = wrapper.find('.ui__vue_hotspot_save') 28 | expect(saveButton.exists()).toEqual(true) 29 | 30 | saveButton.trigger('click') 31 | console.log(wrapper.emitted()) 32 | }) 33 | 34 | it('remove all datapoints', () => { 35 | const removeButton = wrapper.find('.ui__vue_hotspot_remove') 36 | expect(removeButton.exists()).toEqual(true) 37 | 38 | removeButton.trigger('click') 39 | expect(vm.config.data = []) 40 | console.log(wrapper.emitted()) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /src/demo/components/ShowCode.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 52 | 53 | 71 | -------------------------------------------------------------------------------- /src/components/utils/common.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @file utils/common.js 4 | * @author James Wang 5 | */ 6 | 7 | /************************************************************* 8 | * Debounce * 9 | * @param {Function} fn function to be executed * 10 | * @param {Number} wait waiting time to execute the function * 11 | *************************************************************/ 12 | export function debounce (fn, wait) { 13 | let timer = null 14 | return function () { 15 | if (timer !== null) { 16 | clearTimeout(timer) 17 | } 18 | timer = setTimeout(fn, wait) 19 | } 20 | } 21 | 22 | /************************************************************* 23 | * Throttle * 24 | * @param {Function} fn * 25 | * @param {Number} delay delay time * 26 | *************************************************************/ 27 | export function throttle (fn, delay) { 28 | let timer = null 29 | let startTime = Date.now() 30 | return function () { 31 | const self = this 32 | const args = arguments 33 | const curTime = Date.now() 34 | let remaining = delay - (curTime - startTime) 35 | clearTimeout(timer) 36 | if (remaining <= 0) { 37 | fn.apply(self, args) 38 | startTime = Date.now() 39 | } else { 40 | timer = setTimeout(fn, remaining) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/demo/components/Toggle.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 84 | 85 | 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-hotspot", 3 | "version": "2.0.3", 4 | "description": "Hotspot component for Vue.js.", 5 | "author": "James Wang", 6 | "scripts": { 7 | "serve": "vue-cli-service serve", 8 | "build": "cross-env NODE_ENV=production rollup -c", 9 | "lint": "vue-cli-service lint", 10 | "demo": "vue-cli-service build", 11 | "pub": "sh ./publish.sh", 12 | "test:unit": "vue-cli-service test:unit --coverage && codecov", 13 | "test": "vue-cli-service test:unit" 14 | }, 15 | "jest": { 16 | "coverageDirectory": "./coverage/", 17 | "collectCoverage": true 18 | }, 19 | "main": "dist/vue-hotspot.js", 20 | "module": "components/VueHotspot.vue", 21 | "dependencies": { 22 | "@vue/composition-api": "^0.3.2", 23 | "core-js": "^2.6.5", 24 | "vue": "^2.6.10" 25 | }, 26 | "devDependencies": { 27 | "@commitlint/cli": "^11.0.0", 28 | "@commitlint/config-conventional": "^11.0.0", 29 | "@vue/cli-plugin-babel": "^3.11.0", 30 | "@vue/cli-plugin-eslint": "^3.11.0", 31 | "@vue/cli-plugin-unit-jest": "^4.0.5", 32 | "@vue/cli-service": "^4.1.1", 33 | "@vue/eslint-config-standard": "^4.0.0", 34 | "@vue/test-utils": "1.0.0-beta.29", 35 | "babel-core": "7.0.0-bridge.0", 36 | "babel-eslint": "^10.0.1", 37 | "babel-jest": "^25.0.0", 38 | "codecov": "^3.5.0", 39 | "cross-env": "^5.2.1", 40 | "eslint": "^5.16.0", 41 | "eslint-plugin-standard": "^4.0.1", 42 | "eslint-plugin-vue": "^5.0.0", 43 | "husky": "^4.3.0", 44 | "less": "^3.0.4", 45 | "less-loader": "^5.0.0", 46 | "rollup": "^1.20.3", 47 | "rollup-plugin-buble": "^0.19.8", 48 | "rollup-plugin-commonjs": "^10.1.0", 49 | "rollup-plugin-node-resolve": "^5.2.0", 50 | "rollup-plugin-terser": "^5.1.3", 51 | "rollup-plugin-vue": "^5.0.1", 52 | "standard": "^14.1.0", 53 | "vue-template-compiler": "^2.6.10" 54 | }, 55 | "bugs": { 56 | "url": "https://github.com/jamesxwang/vue-hotspot/issues" 57 | }, 58 | "homepage": "https://github.com/jamesxwang/vue-hotspot#readme", 59 | "husky": { 60 | "hooks": { 61 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 62 | "pre-push": "npm run lint && npm run test" 63 | } 64 | }, 65 | "keywords": [ 66 | "vue", 67 | "hotspot", 68 | "component" 69 | ], 70 | "license": "MIT", 71 | "repository": { 72 | "type": "git", 73 | "url": "git+https://github.com/jamesxwang/vue-hotspot.git" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/module/ControlBox.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 37 | 38 | 102 | -------------------------------------------------------------------------------- /src/components/module/DataPoint.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 97 | 98 | 149 | -------------------------------------------------------------------------------- /docs/css/app.88db7626.css: -------------------------------------------------------------------------------- 1 | .ui__vue_hotspot_hotspot[data-v-69a98390]{height:20px;width:20px;position:absolute;border-radius:50%;cursor:pointer;z-index:200;margin-left:-10px;margin-top:-10px}.ui__vue_hotspot_hotspot>div[data-v-69a98390]{width:140px;height:94px;margin:-104px -60px;border-radius:4px;overflow:hidden;font-size:10px;display:none}.ui__vue_hotspot_hotspot.active>div[data-v-69a98390]{display:block}.ui__vue_hotspot_hotspot.active>div[data-v-69a98390]:before{border:solid transparent;content:" ";height:0;left:0;position:absolute;width:0;border-width:10px;border-left-color:hsla(0,0%,100%,.4);-webkit-transform:rotate(90deg);transform:rotate(90deg);top:-10px}.ui__vue_hotspot_hotspot>div>.ui__vue_hotspot_title[data-v-69a98390]{height:20px;line-height:20px;font-weight:700;padding:4px 10px;-webkit-transition:opacity .2s ease-in;transition:opacity .2s ease-in}.ui__vue_hotspot_hotspot>div>.ui__vue_hotspot_message[data-v-69a98390]{margin-top:2px;padding:10px 10px;height:72px;overflow-y:auto;-webkit-transition:opacity .2s ease-in;transition:opacity .2s ease-in}.ui__vue_hotspot_buttons_box[data-v-5089e8e9]{height:5em}.ui__vue_hotspot_buttons[data-v-5089e8e9]{-webkit-transition:padding .4s ease-out,opacity .2s ease-in;transition:padding .4s ease-out,opacity .2s ease-in;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:0 0 1em 1em;padding:0;opacity:0}.ui__vue_hotspot_buttons.active[data-v-5089e8e9]{padding:1em;opacity:1}.ui__vue_hotspot_buttons>.ui__vue_hotspot_remove[data-v-5089e8e9],.ui__vue_hotspot_buttons>.ui__vue_hotspot_save[data-v-5089e8e9]{width:8em;display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #dcdfe6;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:none;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px;margin-left:10px}.ui__vue_hotspot_buttons>.ui__vue_hotspot_save[data-v-5089e8e9]{color:#fff;background-color:#67c23a;border-color:#67c23a}.ui__vue_hotspot_buttons>.ui__vue_hotspot_save[data-v-5089e8e9]:hover{background:#85ce61;border-color:#85ce61;color:#fff}.ui__vue_hotspot_buttons>.ui__vue_hotspot_remove[data-v-5089e8e9]{color:#fff;background-color:#f56c6c;border-color:#f56c6c}.ui__vue_hotspot_buttons>.ui__vue_hotspot_remove[data-v-5089e8e9]:hover{color:#fff;background:#f78989;border-color:#f78989}.ui__vue_hotspot{width:100%;height:100%;display:inline-block;position:relative}.ui__vue_hotspot_background_image{max-width:100%;width:100%}span.ui__vue_hotspot_overlay{position:absolute;background-color:rgba(0,0,0,.4);top:0;left:0;cursor:pointer}span.ui__vue_hotspot_overlay>p{color:#fff;background:hsla(0,0%,100%,.4);margin-top:0;padding:20px;text-align:center}.toggle_button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.label{font-size:1.3em}.switch{position:relative;display:inline-block;width:60px;height:34px}.switch input{opacity:0;width:0;height:0}.slider{cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ccc}.slider,.slider:before{position:absolute;-webkit-transition:.4s;transition:.4s}.slider:before{content:"";height:26px;width:26px;left:4px;bottom:4px;background-color:#fff}input:checked+.slider{background-color:#2196f3}input:focus+.slider{-webkit-box-shadow:0 0 1px #2196f3;box-shadow:0 0 1px #2196f3}input:checked+.slider:before{-webkit-transform:translateX(26px);transform:translateX(26px)}.slider.round{border-radius:34px}.slider.round:before{border-radius:50%}.no-margin{margin:0}.mt-sm{margin-top:1em}.mb-sm{margin-bottom:1em}.ml-sm{margin-left:1em}.mr-sm{margin-right:1em}.mt-m{margin-top:2em}.mb-m{margin-bottom:2em}.ml-m{margin-left:2em}.mr-m{margin-right:2em}.no-padding{padding:0}.pt-sm{padding-top:1em}.pb-sm{padding-bottom:1em}.pl-sm{padding-left:1em}.pr-sm{padding-right:1em}.pt-m{padding-top:2em}.pb-m{padding-bottom:2em}.pl-m{padding-left:2em}.pr-m{padding-right:2em}html{scroll-behavior:smooth}body{margin:0;padding:3em 0 0;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;color:#666;text-align:center}a{color:inherit;text-decoration:none}h1{margin-bottom:1em;font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif}h1,h2{color:#2c3e50;font-weight:300}h2{margin-top:2em;padding-top:1em;font-size:1.2em}button{margin-left:1em;vertical-align:middle}.desc{margin-bottom:3em;color:#7f8c8d}.container{display:inline-block;position:relative;margin:2em auto;border:1px solid rgba(0,0,0,.1);border-radius:8px;-webkit-box-shadow:0 0 45px rgba(0,0,0,.2);box-shadow:0 0 45px rgba(0,0,0,.2);padding:1.5em 2em;width:50vw}.container .text{font-size:1.2em}.container .text.mb-sm{margin-bottom:1em}.container .text .bold{font-weight:700}.container .text .left{float:left}footer{margin:5em 0 3em;font-size:.5em;vertical-align:middle}footer a{display:inline-block;margin:0 5px;padding:3px 0 6px;color:#7f8c8d;font-size:2em;text-decoration:none}footer a:hover{padding-bottom:3px;border-bottom:3px solid #42b983}@media screen and (max-width:980px){section{border-top:1px solid #666}section .container{width:100vw;margin:1em auto;padding:0;border:none;border-radius:0;-webkit-box-shadow:none;box-shadow:none}} -------------------------------------------------------------------------------- /src/demo/Demo.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 104 | 105 | 211 | -------------------------------------------------------------------------------- /src/components/VueHotspot.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 204 | 205 | 232 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | Vue 7 | 8 | 9 | circle-ci 10 | 11 | 12 | Coverage Status 13 | 14 | 15 | npm 16 | 17 | 18 | License 19 | 20 | 21 | GitHub code size in bytes 22 | 23 | 24 | Version 25 | 26 |

27 | 28 | 简体中文 | [English](./README.md) 29 | 30 | ## 简介 31 | 32 | [Vue-Hotspot](https://github.com/jamesxwang/vue-hotspot) 是一个基于Vue.js的图片热点组件。 33 | 34 | * [在线预览](https://jamesxwang.github.io/vue-hotspot/) 35 | 36 | ## 安装 37 | 38 | ### npm (推荐方式) 39 | 40 | ```bash 41 | $ npm install vue-hotspot --save 42 | ``` 43 | 44 | ### yarn 45 | 46 | ``` 47 | $ yarn add vue-hotspot 48 | ``` 49 | 50 | ## 使用方法 51 | 52 | 用 npm 基于 ES Module 引入(推荐用法) 53 | 54 | ```js 55 | import Vue from 'vue' 56 | import VueHotspot from 'vue-hotspot' // 在 webpack 环境下指向 components/VueHotspot.vue 57 | ``` 58 | 59 | ## 调用组件 60 | 61 | ```html 62 | 68 | 69 | 105 | ``` 106 | 107 | ## 在线演示 108 | 109 | 查看[这里](https://jamesxwang.github.io/vue-hotspot/)了解更多例子。 110 | 111 | ## 目录结构 112 |
113 | .
114 | ├── CHANGELOG.md
115 | ├── LICENSE
116 | ├── README.md
117 | ├── README.zh-CN.md
118 | ├── babel.config.js
119 | ├── dist                        // 文件夹, 文件夹 src/components 打包后的代码
120 | ├── docs                        // 文件夹, 文件夹 src/demo 打包后的代码
121 | ├── jest.config.js              // jest 框架的配置信息
122 | ├── package-lock.json
123 | ├── package.json
124 | ├── public                      // 文件夹, 存放demo公共文件
125 | │   ├── favicon.ico
126 | │   └── index.html
127 | ├── publish.sh                  // 发布npm的脚本
128 | ├── rollup.config.js            // rollup 配置信息
129 | ├── src                         // src 文件夹
130 | │   ├── components              // 主文件夹
131 | │   │   ├── VueHotspot.vue      // 入口文件
132 | │   │   ├── module              // module 文件夹
133 | │   │   │   ├── ControlBox.vue
134 | │   │   │   └── DataPoint.vue
135 | │   │   └── utils               // 工具类文件夹
136 | │   │       └── common.js
137 | │   └── demo                    // demo 文件夹
138 | │       ├── Demo.vue
139 | │       ├── assets
140 | │       │   ├── logo.png
141 | │       │   └── unimelb.jpg
142 | │       ├── components
143 | │       │   ├── ShowCode.vue
144 | │       │   └── Toggle.vue
145 | │       └── main.js
146 | ├── tests                       // 单元测试文件文件夹
147 | │   └── unit
148 | │       ├── ControlBox.spec.js
149 | │       ├── DataPoint.spec.js
150 | │       └── common.spec.js
151 | └── vue.config.js               // vue 配置信息
152 | 
153 | 154 | ## 目录结构 155 |
156 | .
157 | ├── CHANGELOG.md
158 | ├── LICENSE
159 | ├── README.md
160 | ├── README.zh-CN.md
161 | ├── babel.config.js
162 | ├── dist                        // 文件夹, 文件夹 src/components 打包后的代码
163 | ├── docs                        // 文件夹, 文件夹 src/demo 打包后的代码
164 | ├── jest.config.js              // jest 框架的配置信息
165 | ├── package-lock.json
166 | ├── package.json
167 | ├── public                      // 文件夹, 存放demo公共文件
168 | │   ├── favicon.ico
169 | │   └── index.html
170 | ├── publish.sh                  // 发布npm的脚本
171 | ├── rollup.config.js            // rollup 配置信息
172 | ├── src                         // src 文件夹
173 | │   ├── components              // 主文件夹
174 | │   │   ├── VueHotspot.vue      // 入口文件
175 | │   │   ├── module              // module 文件夹
176 | │   │   │   ├── ControlBox.vue
177 | │   │   │   └── DataPoint.vue
178 | │   │   └── utils               // 工具类文件夹
179 | │   │       └── common.js
180 | │   └── demo                    // demo 文件夹
181 | │       ├── Demo.vue
182 | │       ├── assets
183 | │       │   ├── logo.png
184 | │       │   └── unimelb.jpg
185 | │       ├── components
186 | │       │   ├── ShowCode.vue
187 | │       │   └── Toggle.vue
188 | │       └── main.js
189 | ├── tests                       // 单元测试文件文件夹
190 | │   └── unit
191 | │       ├── ControlBox.spec.js
192 | │       ├── DataPoint.spec.js
193 | │       └── common.spec.js
194 | └── vue.config.js               // vue 配置信息
195 | 
196 | 197 | ## 配置选项 198 | 199 | | 配置选项 | 简介 | 是否必须 | 默认值 | 200 | |:---------------:|-----------------------------------------------------------------------------------------------------------------------|:--------:|:------------------------------------------:| 201 | | image | 背景图片 | 是 | 一张带有'Oops! image not found...'字的图片 | 202 | | data | 存放热点的数据.
数据结构: `[ Message: 'String', Title: 'String, x: Float, y: Float' ]` | 否 | [] | 203 | | editable | 指定要在其中使用插件的可编辑项。
`true`: 用户可以通过组件UI创建新的热点。
`false`: 组件从`data`对象中读取数据。 | 否 | true | 204 | | interactivity | 显示热点的条件事件,可允许设置值: `click`, `hover`, `none` | 否 | hover | 205 | | hotspotColor | 热点的颜色 | 否 | 'rgb(66, 184, 131)' | 206 | | messageBoxColor | 热点消息框的颜色 | 否 | 'rgb(255, 255, 255)' | 207 | | textColor | 热点消息框中字的颜色 | 否 | 'rgb(53, 73, 94)' | 208 | | opacity | 热点消息框的透明度 | 否 | 0.8 | 209 | | overlayText | 编辑模式下的覆盖层的文字 | 否 | 'Please Click The Image To Add Hotspots.' | 210 | 211 | ## 本地开发 212 | 213 | ```bash 214 | $ npm i 215 | $ npm run serve 216 | ``` 217 | 218 | 打开 `http://localhost:8080/` 来查看例子。 219 | 220 | # 版权说明 221 | 222 | Vue-hotspot 组件的证书是 [MIT 证书](https://github.com/jamesxwang/vue-hotspot/blob/master/LICENSE) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | Vue 7 | 8 | 9 | circle-ci 10 | 11 | 12 | Coverage Status 13 | 14 | 15 | npm 16 | 17 | 18 | License 19 | 20 | 21 | GitHub code size in bytes 22 | 23 | 24 | Version 25 | 26 |

27 | 28 | English | [简体中文](./README.zh-CN.md) 29 | 30 | ## Introduction 31 | 32 | [Vue-Hotspot](https://github.com/jamesxwang/vue-hotspot) is an image hotspot component for Vue.js. 33 | 34 | * [Live Demo](https://jamesxwang.github.io/vue-hotspot/) 35 | 36 | ## Installation 37 | 38 | ### npm (Recommended) 39 | 40 | ```bash 41 | $ npm install vue-hotspot --save 42 | ``` 43 | 44 | ### yarn 45 | 46 | ``` 47 | $ yarn add vue-hotspot 48 | ``` 49 | 50 | ## Usage 51 | 52 | ES Modules with npm (Recommended) 53 | 54 | ```js 55 | import Vue from 'vue' 56 | import VueHotspot from 'vue-hotspot' // refers to components/VueHotspot.vue in webpack 57 | ``` 58 | 59 | ## Using the component 60 | 61 | ```html 62 | 68 | 69 | 105 | ``` 106 | 107 | ## Live Demo 108 | 109 | You can see more examples [here](https://jamesxwang.github.io/vue-hotspot/). 110 | 111 | ## Project Structure 112 |
113 | .
114 | ├── CHANGELOG.md
115 | ├── LICENSE
116 | ├── README.md
117 | ├── README.zh-CN.md
118 | ├── babel.config.js
119 | ├── dist                        // folder, build files from src/components
120 | ├── docs                        // folder, build files from src/demo
121 | ├── jest.config.js              // jest config
122 | ├── package-lock.json
123 | ├── package.json
124 | ├── public                      // folder, demo public files
125 | │   ├── favicon.ico
126 | │   └── index.html
127 | ├── publish.sh                  // publish shell script
128 | ├── rollup.config.js            // rollup config
129 | ├── src                         // folder, src folder
130 | │   ├── components              // folder, main folder
131 | │   │   ├── VueHotspot.vue      // entry file
132 | │   │   ├── module              // folder, module folder
133 | │   │   │   ├── ControlBox.vue
134 | │   │   │   └── DataPoint.vue
135 | │   │   └── utils               // folder, utils folder
136 | │   │       └── common.js
137 | │   └── demo                    // folder, demo files source folder
138 | │       ├── Demo.vue
139 | │       ├── assets
140 | │       │   ├── logo.png
141 | │       │   └── unimelb.jpg
142 | │       ├── components
143 | │       │   ├── ShowCode.vue
144 | │       │   └── Toggle.vue
145 | │       └── main.js
146 | ├── tests                       // folder, unit test folder
147 | │   └── unit
148 | │       ├── ControlBox.spec.js
149 | │       ├── DataPoint.spec.js
150 | │       └── common.spec.js
151 | └── vue.config.js               // vue config file
152 | 
153 | 154 | ## Project Structure 155 |
156 | .
157 | ├── CHANGELOG.md
158 | ├── LICENSE
159 | ├── README.md
160 | ├── README.zh-CN.md
161 | ├── babel.config.js
162 | ├── dist                        // folder, build files from src/components
163 | ├── docs                        // folder, build files from src/demo
164 | ├── jest.config.js              // jest config
165 | ├── package-lock.json
166 | ├── package.json
167 | ├── public                      // folder, demo public files
168 | │   ├── favicon.ico
169 | │   └── index.html
170 | ├── publish.sh                  // publish shell script
171 | ├── rollup.config.js            // rollup config
172 | ├── src                         // folder, src folder
173 | │   ├── components              // folder, main folder
174 | │   │   ├── VueHotspot.vue      // entry file
175 | │   │   ├── module              // folder, module folder
176 | │   │   │   ├── ControlBox.vue
177 | │   │   │   └── DataPoint.vue
178 | │   │   └── utils               // folder, utils folder
179 | │   │       └── common.js
180 | │   └── demo                    // folder, demo files source folder
181 | │       ├── Demo.vue
182 | │       ├── assets
183 | │       │   ├── logo.png
184 | │       │   └── unimelb.jpg
185 | │       ├── components
186 | │       │   ├── ShowCode.vue
187 | │       │   └── Toggle.vue
188 | │       └── main.js
189 | ├── tests                       // folder, unit test folder
190 | │   └── unit
191 | │       ├── ControlBox.spec.js
192 | │       ├── DataPoint.spec.js
193 | │       └── common.spec.js
194 | └── vue.config.js               // vue config file
195 | 
196 | 197 | ## Config Options 198 | 199 | | options | description | required | default | 200 | |:---------------:|---------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:---------------------------------------------------:| 201 | | image | Default image placeholder | true | an empty image with text 'Oops! image not found...' | 202 | | data | Object to hold the hotspot data points.
Data structure: `[ {Message: 'String', Title: 'String, x: Float, y: Float'} ]` | false | [] | 203 | | editable | Specify editable in which the plugin is to be used.
`true`: Allows to create hotspots from UI.
`false`: Display hotspots from `data` object | false | true | 204 | | interactivity | Event on which the hotspot data point will show up.
allowed values: `click`, `hover`, `none` | false | hover | 205 | | hotspotColor | background color for hotspot dots | false | 'rgb(66, 184, 131)' | 206 | | messageBoxColor | background color for hotspot message boxes | false | 'rgb(255, 255, 255)' | 207 | | textColor | background color for hotspot text | false | 'rgb(53, 73, 94)' | 208 | | opacity | opacity for hotspots | false | 0.8 | 209 | | overlayText | text for overlay in edit mode | false | 'Please Click The Image To Add Hotspots.' | 210 | 211 | ## Local development 212 | 213 | ```bash 214 | $ npm i 215 | $ npm run serve 216 | ``` 217 | 218 | Open `http://localhost:8080/` to see the demo. 219 | 220 | # License 221 | 222 | Vue-hotspot component is delivered under the [MIT License](https://github.com/jamesxwang/vue-hotspot/blob/master/LICENSE) -------------------------------------------------------------------------------- /dist/vue-hotspot.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("vue"),require("@vue/composition-api")):"function"==typeof define&&define.amd?define(["vue","@vue/composition-api"],e):(t=t||self).VueHotspot=e(t.Vue,t.VueCompositionApi)}(this,(function(t,e){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var o="default"in e?e.default:e;function i(t,e){var o=null,i=Date.now();return function(){var n=this,a=arguments,s=Date.now(),r=e-(s-i);clearTimeout(o),r<=0?(t.apply(n,a),i=Date.now()):o=setTimeout(t,r)}}var n=e.createComponent({props:{hotspot:Object,config:Object,imageLoaded:Boolean,vueHotspotBackgroundImage:HTMLImageElement,vueHotspot:HTMLDivElement},setup:function(t,o){o.emit;var n=e.ref(!1),a=e.reactive({positionTop:0,positionLeft:0,hotspotColor:e.computed((function(){return t.config&&t.config.hotspotColor})),interactivity:e.computed((function(){return t.config&&t.config.interactivity})),textColor:e.computed((function(){return t.config&&t.config.textColor})),messageBoxColor:e.computed((function(){return t.config&&t.config.messageBoxColor})),opacity:e.computed((function(){return t.config&&t.config.opacity}))});function s(){a.positionTop=t.hotspot.y*t.vueHotspotBackgroundImage.clientHeight/100+(t.vueHotspotBackgroundImage.offsetTop-t.vueHotspot.clientTop)+"px;",a.positionLeft=t.hotspot.x*t.vueHotspotBackgroundImage.clientWidth/100+(t.vueHotspotBackgroundImage.offsetLeft-t.vueHotspot.clientLeft)+"px;"}return e.watch((function(){return t.imageLoaded}),(function(t,e){t&&s()})),e.onMounted((function(){window.addEventListener("resize",i(s,50))})),e.onUnmounted((function(){window.removeEventListener("resize",i(s,50))})),Object.assign({},{isActive:n},e.toRefs(a),{getHotspotStyle:s,toggleActive:function(){n.value=!n.value}})}});function a(t,e,o,i,n,a,s,r,u,c){"boolean"!=typeof s&&(u=r,r=s,s=!1);var p,d="function"==typeof o?o.options:o;if(t&&t.render&&(d.render=t.render,d.staticRenderFns=t.staticRenderFns,d._compiled=!0,n&&(d.functional=!0)),i&&(d._scopeId=i),a?(p=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),e&&e.call(this,u(t)),t&&t._registeredComponents&&t._registeredComponents.add(a)},d._ssrRegister=p):e&&(p=s?function(t){e.call(this,c(t,this.$root.$options.shadowRoot))}:function(t){e.call(this,r(t))}),p)if(d.functional){var l=d.render;d.render=function(t,e){return p.call(e),l(t,e)}}else{var v=d.beforeCreate;d.beforeCreate=v?[].concat(v,p):[p]}return o}var s,r="undefined"!=typeof navigator&&/msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());function u(t){return function(t,e){return function(t,e){var o=r?e.media||"default":t,i=c[o]||(c[o]={ids:new Set,styles:[]});if(!i.ids.has(t)){i.ids.add(t);var n=e.source;if(e.map&&(n+="\n/*# sourceURL="+e.map.sources[0]+" */",n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e.map))))+" */"),i.element||(i.element=document.createElement("style"),i.element.type="text/css",e.media&&i.element.setAttribute("media",e.media),void 0===s&&(s=document.head||document.getElementsByTagName("head")[0]),s.appendChild(i.element)),"styleSheet"in i.element)i.styles.push(n),i.element.styleSheet.cssText=i.styles.filter(Boolean).join("\n");else{var a=i.ids.size-1,u=document.createTextNode(n),p=i.element.childNodes;p[a]&&i.element.removeChild(p[a]),p.length?i.element.insertBefore(u,p[a]):i.element.appendChild(u)}}}(t,e)}}var c={};var p=a({render:function(){var t=this,e=t.$createElement,o=t._self._c||e;return o("div",{staticClass:"ui__vue_hotspot_hotspot",class:t.isActive||"none"===t.interactivity?"active":"",style:"top: "+t.positionTop+"; left: "+t.positionLeft+"; background-color: "+t.hotspotColor+";",on:{mouseenter:function(e){"hover"===t.interactivity&&(t.isActive=!0)},mouseleave:function(e){"hover"===t.interactivity&&(t.isActive=!1)},click:function(e){"click"===t.interactivity&&t.toggleActive()}}},[o("div",{style:"color:"+t.textColor},[o("div",{staticClass:"ui__vue_hotspot_title",style:"\n background: "+t.messageBoxColor+";\n opacity: "+t.opacity},[t._v("\n "+t._s(t.hotspot.Title)+"\n ")]),t._v(" "),o("div",{staticClass:"ui__vue_hotspot_message",style:"\n background: "+t.messageBoxColor+";\n opacity: "+t.opacity},[t._v("\n "+t._s(t.hotspot.Message)+"\n ")])])])},staticRenderFns:[]},(function(t){t&&t("data-v-243de032_0",{source:".ui__vue_hotspot_hotspot[data-v-243de032]{height:20px;width:20px;position:absolute;border-radius:50%;cursor:pointer;z-index:200;margin-left:-10px;margin-top:-10px}.ui__vue_hotspot_hotspot>div[data-v-243de032]{width:140px;height:94px;margin:-104px -60px;border-radius:4px;overflow:hidden;font-size:10px;display:none}.ui__vue_hotspot_hotspot.active>div[data-v-243de032]{display:block}.ui__vue_hotspot_hotspot.active>div[data-v-243de032]:before{border:solid transparent;content:' ';height:0;left:0;position:absolute;width:0;border-width:10px;border-left-color:rgba(255,255,255,.4);transform:rotate(90deg);top:-10px}.ui__vue_hotspot_hotspot>div>.ui__vue_hotspot_title[data-v-243de032]{height:20px;line-height:20px;font-weight:700;padding:4px 10px;transition:opacity .2s ease-in}.ui__vue_hotspot_hotspot>div>.ui__vue_hotspot_message[data-v-243de032]{margin-top:2px;padding:10px 10px;height:72px;overflow-y:auto;transition:opacity .2s ease-in}",map:void 0,media:void 0})}),n,"data-v-243de032",!1,void 0,!1,u,void 0,void 0),d=a({render:function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"ui__vue_hotspot_buttons_box"},[e("div",{staticClass:"ui__vue_hotspot_buttons",class:this.isEditable?"active":""},[e("button",{staticClass:"ui__vue_hotspot_save",on:{click:this.saveAllHotspots}},[this._v("Save")]),this._v(" "),e("button",{staticClass:"ui__vue_hotspot_remove",on:{click:this.removeAllHotspots}},[this._v("Remove")])])])},staticRenderFns:[]},(function(t){t&&t("data-v-74ac8b34_0",{source:".ui__vue_hotspot_buttons_box[data-v-74ac8b34]{height:5em}.ui__vue_hotspot_buttons[data-v-74ac8b34]{transition:padding .4s ease-out,opacity .2s ease-in;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:0 0 1em 1em;padding:0;opacity:0}.ui__vue_hotspot_buttons.active[data-v-74ac8b34]{padding:1em;opacity:1}.ui__vue_hotspot_buttons>.ui__vue_hotspot_remove[data-v-74ac8b34],.ui__vue_hotspot_buttons>.ui__vue_hotspot_save[data-v-74ac8b34]{width:8em;display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #dcdfe6;color:#606266;-webkit-appearance:none;text-align:center;box-sizing:border-box;outline:0;margin:0;transition:.1s;font-weight:500;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px;margin-left:10px}.ui__vue_hotspot_buttons>.ui__vue_hotspot_save[data-v-74ac8b34]{color:#fff;background-color:#67c23a;border-color:#67c23a}.ui__vue_hotspot_buttons>.ui__vue_hotspot_save[data-v-74ac8b34]:hover{background:#85ce61;border-color:#85ce61;color:#fff}.ui__vue_hotspot_buttons>.ui__vue_hotspot_remove[data-v-74ac8b34]{color:#fff;background-color:#f56c6c;border-color:#f56c6c}.ui__vue_hotspot_buttons>.ui__vue_hotspot_remove[data-v-74ac8b34]:hover{color:#fff;background:#f78989;border-color:#f78989}",map:void 0,media:void 0})}),e.createComponent({props:{config:Object},setup:function(t,o){var i=o.emit;return{isEditable:e.computed((function(){return t.config&&t.config.editable})),saveAllHotspots:function(){i("save-data",t.config.data)},removeAllHotspots:function(){t.config.data=[],i("after-delete")}}}}),"data-v-74ac8b34",!1,void 0,!1,u,void 0,void 0);t.use(o);return a({render:function(){var t=this,e=t.$createElement,o=t._self._c||e;return t.config?o("div",{ref:"vueHotspot",staticClass:"ui__vue_hotspot"},[o("img",{ref:"vueHotspotBackgroundImage",staticClass:"ui__vue_hotspot_background_image",attrs:{src:t.config.image,alt:"Hotspot Image"},on:{load:t.successLoadImg}}),t._v(" "),t.config.editable?o("span",{ref:"vueHotspotOverlay",staticClass:"ui__vue_hotspot_overlay",style:"height: "+t.overlayHeight+"; width: "+t.overlayWidth+"; left: "+t.overlayLeft+"; top: "+t.overlayTop+";",on:{click:function(e){return e.stopPropagation(),e.preventDefault(),t.addHotspot(e)}}},[o("p",[t._v(t._s(t.config.overlayText))])]):t._e(),t._v(" "),t._l(t.config.data,(function(e,i){return o("DataPoint",{key:i,attrs:{hotspot:e,config:t.config,imageLoaded:t.imageLoaded,vueHotspotBackgroundImage:t.vueHotspotBackgroundImage,vueHotspot:t.vueHotspot}})})),t._v(" "),o("ControlBox",{attrs:{config:t.config},on:{"save-data":t.saveAllHotspots,"after-delete":t.removeAllHotspots}})],2):t._e()},staticRenderFns:[]},(function(t){t&&t("data-v-6ee6204c_0",{source:".ui__vue_hotspot{width:100%;height:100%;display:inline-block;position:relative}.ui__vue_hotspot_background_image{max-width:100%;width:100%}span.ui__vue_hotspot_overlay{position:absolute;background-color:rgba(0,0,0,.4);top:0;left:0;cursor:pointer}span.ui__vue_hotspot_overlay>p{color:#fff;background:rgba(255,255,255,.4);margin-top:0;padding:20px;text-align:center}",map:void 0,media:void 0})}),e.createComponent({components:{DataPoint:p,ControlBox:d},props:{initOptions:Object},setup:function(t,o){var n=o.emit,a=e.ref(null),s=e.ref(null),r=e.ref(null),u=e.reactive({data:[],image:"https://via.placeholder.com/600x500?text=Oops!+image+not+found...",editable:!0,interactivity:"hover",hotspotColor:"rgb(66, 184, 131)",messageBoxColor:"rgb(255, 255, 255)",textColor:"rgb(53, 73, 94)",overlayText:"Please Click The Image To Add Hotspots.",opacity:.8,schema:[{property:"Title",default:"Vue Hotspot"},{property:"Message",default:"This is a Vue Hotspot Component which lets you create hotspot. "}]}),c=e.ref(null),p=e.ref(!1),d=e.reactive({overlayHeight:0,overlayWidth:0,overlayLeft:0,overlayTop:0});function l(){var t=e.isRef(r)?r.value:r,o=e.isRef(a)?a.value:a;d.overlayHeight=t.clientHeight/o.clientHeight*100+"%",d.overlayWidth=t.clientWidth/o.clientWidth*100+"%",d.overlayLeft=t.offsetLeft-o.clientLeft+"px",d.overlayTop=t.offsetTop-o.clientTop+"px"}function v(t){return JSON.parse(JSON.stringify(t))}return e.watch(p,(function(t,e){t&&l()})),e.watch((function(){return t.initOptions}),(function(t,e){c.value=Object.assign({},c.value?c.value:v(u),t)}),{deep:!0}),e.onMounted((function(){window.addEventListener("resize",i(l,50))})),e.onUnmounted((function(){window.removeEventListener("resize",i(l,50))})),Object.assign({},{defaultOptions:u,config:c,imageLoaded:p},e.toRefs(d),{vueHotspot:a,vueHotspotOverlay:s,vueHotspotBackgroundImage:r,deepCopy:v,successLoadImg:function(t){!0===t.target.complete&&(p.value=!0)},saveAllHotspots:function(){var t=e.isRef(c)?c.value:c;n("save-data",t.data)},removeAllHotspots:function(){n("after-delete")},resizeOverlay:l,addHotspot:function(t){for(var o=t.offsetX,i=t.offsetY,n=e.isRef(c)?c.value:c,a=s.value.offsetHeight,r={x:o/s.value.offsetWidth*100,y:i/a*100},u=n.schema,p=0;p\n */\n\n/*************************************************************\n * Debounce *\n * @param {Function} fn function to be executed *\n * @param {Number} wait waiting time to execute the function *\n *************************************************************/\nexport function debounce (fn, wait) {\n let timer = null\n return function () {\n if (timer !== null) {\n clearTimeout(timer)\n }\n timer = setTimeout(fn, wait)\n }\n}\n\n/*************************************************************\n * Throttle *\n * @param {Function} fn *\n * @param {Number} delay delay time *\n *************************************************************/\nexport function throttle (fn, delay) {\n let timer = null\n let startTime = Date.now()\n return function () {\n const self = this\n const args = arguments\n const curTime = Date.now()\n let remaining = delay - (curTime - startTime)\n clearTimeout(timer)\n if (remaining <= 0) {\n fn.apply(self, args)\n startTime = Date.now()\n } else {\n timer = setTimeout(fn, remaining)\n }\n }\n}\n","\n\n\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./DataPoint.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./DataPoint.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./DataPoint.vue?vue&type=template&id=69a98390&scoped=true&\"\nimport script from \"./DataPoint.vue?vue&type=script&lang=js&\"\nexport * from \"./DataPoint.vue?vue&type=script&lang=js&\"\nimport style0 from \"./DataPoint.vue?vue&type=style&index=0&id=69a98390&scoped=true&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"69a98390\",\n null\n \n)\n\nexport default component.exports","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"ui__vue_hotspot_buttons_box\"},[_c('div',{staticClass:\"ui__vue_hotspot_buttons\",class:_vm.isEditable ? 'active' : ''},[_c('button',{staticClass:\"ui__vue_hotspot_save\",on:{\"click\":_vm.saveAllHotspots}},[_vm._v(\"Save\")]),_c('button',{staticClass:\"ui__vue_hotspot_remove\",on:{\"click\":_vm.removeAllHotspots}},[_vm._v(\"Remove\")])])])}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./ControlBox.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./ControlBox.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./ControlBox.vue?vue&type=template&id=5089e8e9&scoped=true&\"\nimport script from \"./ControlBox.vue?vue&type=script&lang=js&\"\nexport * from \"./ControlBox.vue?vue&type=script&lang=js&\"\nimport style0 from \"./ControlBox.vue?vue&type=style&index=0&id=5089e8e9&scoped=true&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"5089e8e9\",\n null\n \n)\n\nexport default component.exports","\n\n\n\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./VueHotspot.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./VueHotspot.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./VueHotspot.vue?vue&type=template&id=525f3fd4&\"\nimport script from \"./VueHotspot.vue?vue&type=script&lang=js&\"\nexport * from \"./VueHotspot.vue?vue&type=script&lang=js&\"\nimport style0 from \"./VueHotspot.vue?vue&type=style&index=0&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"toggle_button\"},[_c('p',{staticClass:\"label\"},[_vm._v(_vm._s(_vm.label)+\": \")]),_c('p',[_c('label',{staticClass:\"switch\"},[_c('input',{attrs:{\"type\":\"checkbox\"},domProps:{\"checked\":_vm.hotspotConfig.editable},on:{\"change\":_vm.handleToggleChange}}),_c('span',{staticClass:\"slider round\"})])])])}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Toggle.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Toggle.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./Toggle.vue?vue&type=template&id=be0be6f6&\"\nimport script from \"./Toggle.vue?vue&type=script&lang=js&\"\nexport * from \"./Toggle.vue?vue&type=script&lang=js&\"\nimport style0 from \"./Toggle.vue?vue&type=style&index=0&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Demo.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Demo.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./Demo.vue?vue&type=template&id=7c1c9da7&\"\nimport script from \"./Demo.vue?vue&type=script&lang=js&\"\nexport * from \"./Demo.vue?vue&type=script&lang=js&\"\nimport style0 from \"./Demo.vue?vue&type=style&index=0&lang=less&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import Vue from 'vue'\nimport Demo from './Demo.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n render: h => h(Demo)\n}).$mount('#app')\n","import mod from \"-!../../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Toggle.vue?vue&type=style&index=0&lang=css&\"; export default mod; export * from \"-!../../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Toggle.vue?vue&type=style&index=0&lang=css&\"","import mod from \"-!../../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./ControlBox.vue?vue&type=style&index=0&id=5089e8e9&scoped=true&lang=css&\"; export default mod; export * from \"-!../../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./ControlBox.vue?vue&type=style&index=0&id=5089e8e9&scoped=true&lang=css&\"","import mod from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./VueHotspot.vue?vue&type=style&index=0&lang=css&\"; export default mod; export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./VueHotspot.vue?vue&type=style&index=0&lang=css&\"","module.exports = __webpack_public_path__ + \"img/logo.4bd586bc.png\";","import mod from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--10-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--10-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--10-oneOf-1-2!../../node_modules/less-loader/dist/cjs.js??ref--10-oneOf-1-3!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Demo.vue?vue&type=style&index=0&lang=less&\"; export default mod; export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--10-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--10-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--10-oneOf-1-2!../../node_modules/less-loader/dist/cjs.js??ref--10-oneOf-1-3!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Demo.vue?vue&type=style&index=0&lang=less&\""],"sourceRoot":""} --------------------------------------------------------------------------------