├── .gitignore ├── README.md ├── babel.config.js ├── dist ├── css │ └── vue-magnifier.css ├── js │ └── vue-magnifier.js └── scss │ └── vue-magnifier.scss ├── package-lock.json ├── package.json ├── public └── index.html └── src ├── App.vue ├── components └── vue-magnifier.vue └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue-Magnifier: a simple VueJS 2.x component 2 | 3 | For a demo, please visit here: https://codepen.io/zeknoss/pen/vaxGKe 4 | 5 | > Vue Magnifier is free component for basic image zoom practices. 6 | > You can use it as follows: 7 | ``` html 8 | 9 | 10 | To customize the look and feel of the component, just edit the vue component file, or the provided standalone vue-magnifier.scss or the vue-magnifier.css file. 11 | ``` 12 | 13 | ## Load standalone from CDN 14 | ``` html 15 | 16 | 17 | ``` 18 | 19 | ## Load standalone from local 20 | ``` html 21 | 22 | 23 | ``` 24 | 25 | ## Build Setup 26 | 27 | ``` bash 28 | # install dependencies 29 | npm install 30 | 31 | # serve with hot reload at localhost:8080 32 | npm run dev 33 | 34 | # build for production with minification 35 | npm run build 36 | 37 | # build for production and view the bundle analyzer report 38 | npm run build --report 39 | 40 | # run unit tests 41 | npm run unit 42 | 43 | # run all tests 44 | npm test 45 | ``` 46 | 47 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 48 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /dist/css/vue-magnifier.css: -------------------------------------------------------------------------------- 1 | 2 | .vue-magnifier-container { 3 | position: relative; 4 | } 5 | .vue-magnifier-container .preview { 6 | position: relative; 7 | background-repeat: no-repeat; 8 | background-size: contain; 9 | background-position: 50% 50%; 10 | display: block; 11 | clear: both; 12 | margin: 0 auto; 13 | cursor: none; 14 | } 15 | .vue-magnifier-container .preview .magnifying-glass { 16 | position: absolute; 17 | border: 5px solid #8FD6EF; 18 | border-radius: 50%; 19 | cursor: none; 20 | width: 150px; 21 | height: 150px; 22 | transform: translate(-75px, -75px); 23 | background: #fff no-repeat; 24 | display: none; 25 | pointer-events: none; 26 | } 27 | .vue-magnifier-container .preview:hover .magnifying-glass { 28 | display: block; 29 | } 30 | @media only screen and (max-width: 320px) { 31 | .vue-magnifier-container .preview { 32 | width: 130px; 33 | height: 233px; 34 | } 35 | } 36 | @media only screen and (max-width: 480px) { 37 | .vue-magnifier-container .preview { 38 | width: 170px; 39 | height: 300px; 40 | } 41 | } 42 | @media only screen and (min-width: 481px) { 43 | .vue-magnifier-container .preview { 44 | width: 260px; 45 | height: 450px; 46 | } 47 | } 48 | @media only screen and (min-width: 481px) and (min-height: 769px) and (max-width: 1023px) { 49 | .vue-magnifier-container .preview { 50 | width: 260px; 51 | height: 520px; 52 | } 53 | } 54 | @media only screen and (min-width: 1024px) { 55 | .vue-magnifier-container .preview { 56 | width: 260px; 57 | height: 450px; 58 | } 59 | } 60 | @media only screen and (min-width: 1280px) { 61 | .vue-magnifier-container .preview { 62 | width: 300px; 63 | height: 520px; 64 | } 65 | } 66 | @media only screen and (min-width: 1280px) and (max-height: 769px) { 67 | .vue-magnifier-container .preview { 68 | width: 245px; 69 | height: 430px; 70 | } 71 | } -------------------------------------------------------------------------------- /dist/js/vue-magnifier.js: -------------------------------------------------------------------------------- 1 | Vue.component('vue-magnifier', { 2 | template: 3 | '
' + 4 | '' + 5 | '' + 6 | '' + 11 | '' + 12 | '
', 13 | props: { 14 | src: String, 15 | srcLarge: String, 16 | }, 17 | computed: { 18 | glassStyle() { 19 | return { 20 | backgroundImage: `url(${this.srcLarge})`, 21 | backgroundPosition: this.backgroundPos, 22 | left: `${this.cursorX}px`, 23 | top: this.cursorY + 'px', 24 | }; 25 | } 26 | }, 27 | methods: { 28 | getCursorPos(e) { 29 | let x = window.Event 30 | ? e.pageX 31 | : e.clientX; 32 | x -= (document.documentElement.scrollLeft) ? 33 | document.documentElement.scrollLeft 34 | : document.body.scrollLeft; 35 | let y = window.Event 36 | ? e.pageY 37 | : e.clientY; 38 | y -= (document.documentElement.scrollTop) ? 39 | document.documentElement.scrollTop 40 | : document.body.scrollTop; 41 | 42 | this.cursorX = x - this.thumbPos.x; 43 | this.cursorY = y - this.thumbPos.y; 44 | }, 45 | getBounds() { 46 | let el = this.$refs.magnificationElement; 47 | 48 | this.bounds = el.getBoundingClientRect(); 49 | 50 | let xPos = 0; 51 | let yPos = 0; 52 | while (el) { 53 | const transform = this.getTransform(el); 54 | if (el.tagName === 'BODY') { 55 | // deal with browser quirks with body/window/document and page scroll 56 | const xScroll = el.scrollLeft || document.documentElement.scrollLeft; 57 | const yScroll = el.scrollTop || document.documentElement.scrollTop; 58 | 59 | xPos += el.offsetLeft - xScroll + el.clientLeft + parseInt(transform[0]); 60 | yPos += el.offsetTop - yScroll + el.clientTop + parseInt(transform[1]); 61 | } else { 62 | // for all other non-BODY elements 63 | xPos += el.offsetLeft - el.scrollLeft + el.clientLeft + parseInt(transform[0]); 64 | yPos += el.offsetTop - el.scrollTop + el.clientTop + parseInt(transform[1]); 65 | } 66 | 67 | el = el.offsetParent; 68 | } 69 | this.thumbPos = { 70 | x: xPos, 71 | y: yPos, 72 | }; 73 | }, 74 | moveMagnifier(e) { 75 | e.preventDefault(); 76 | 77 | this.getBounds(); 78 | this.getCursorPos(e); 79 | 80 | this.backgroundPos = `${(this.cursorX * 100) / this.bounds.width}% ${(this.cursorY * 100) / this.bounds.height}%`; 81 | }, 82 | getTransform(el) { 83 | const transform = window 84 | .getComputedStyle(el, null) 85 | .getPropertyValue('-webkit-transform'); 86 | 87 | function rotateDegree(matrix) { 88 | let angle; 89 | if (matrix !== 'none') { 90 | const values = matrix 91 | .split('(')[1] 92 | .split(')')[0] 93 | .split(','); 94 | const a = values[0]; 95 | const b = values[1]; 96 | angle = Math.round(Math.atan2(b, a) * (180 / Math.PI)); 97 | } else { 98 | angle = 0; 99 | } 100 | // eslint-disable-next-line no-return-assign 101 | return angle < 0 ? (angle += 360) : angle; 102 | } 103 | 104 | const results = transform.match( 105 | /matrix(?:(3d)\(-{0,1}\d+\.?\d*(?:, -{0,1}\d+\.?\d*)*(?:, (-{0,1}\d+\.?\d*))(?:, (-{0,1}\d+\.?\d*))(?:, (-{0,1}\d+\.?\d*)), -{0,1}\d+\.?\d*\)|\(-{0,1}\d+\.?\d*(?:, -{0,1}\d+\.?\d*)*(?:, (-{0,1}\d+\.?\d*))(?:, (-{0,1}\d+\.?\d*))\))/, 106 | ); 107 | 108 | let output = [0, 0, 0]; 109 | if (results) { 110 | if (results[1] === '3d') { 111 | output = results.slice(2, 5); 112 | } else { 113 | results.push(0); 114 | output = results.slice(5, 9); // returns the [X,Y,Z,1] value; 115 | } 116 | 117 | output.push(rotateDegree(transform)); 118 | } 119 | return output; 120 | }, 121 | }, 122 | mounted(){ 123 | this.$nextTick(function () { 124 | this.$refs.magnificationElement.addEventListener( 125 | 'mousemove', 126 | this.moveMagnifier, 127 | ); 128 | }); 129 | }, 130 | data(){ 131 | return { 132 | img: null, 133 | width: null, 134 | height: null, 135 | bounds: null, 136 | cursorX: 0, 137 | cursorY: 0, 138 | thumbPos: {x: 0, y: 0}, 139 | backgroundPos: '0 0', 140 | }; 141 | } 142 | }); 143 | -------------------------------------------------------------------------------- /dist/scss/vue-magnifier.scss: -------------------------------------------------------------------------------- 1 | // Magnifying glass options 2 | $border-size: 5px; // Modify the border width of the magnifying glass component 3 | $border-color: #666666; // Modify the border color of the magnifying glass component 4 | $magnifier-width: 150px; // Modify the width of the magnifying glass component 5 | $magnifier-height: 150px; // Modify the height of the magnifying glass component 6 | 7 | // Define your responsive sizes of 8 | $sizes: ( 9 | "(max-width: 320px)" 250px 250px, 10 | "(max-width: 480px)" 350px 350px, 11 | "(min-width: 481px)" 450px 450px, 12 | "(min-width: 1024px)" 550px 550px, 13 | "(min-width: 1280px)" 600px 600px 14 | ); 15 | 16 | .vue-magnifier-container { 17 | position: relative; 18 | .preview { 19 | position: relative; 20 | background: { 21 | repeat: no-repeat; 22 | size: contain; 23 | position: 50% 50%; 24 | } 25 | display: block; 26 | clear: both; 27 | margin: 0 auto; 28 | cursor: none; 29 | 30 | .magnifying-glass { 31 | position: absolute; 32 | border: $border-size solid $border-color; 33 | border-radius: 50%; 34 | cursor: none; 35 | width: $magnifier-width; 36 | height: $magnifier-height; 37 | transform: translate( 38 | (-1 * $magnifier-width/2), 39 | (-1 * $magnifier-width/2) 40 | ); 41 | background: #fff no-repeat; 42 | display: none; 43 | pointer-events: none; 44 | } 45 | 46 | &:hover { 47 | .magnifying-glass { 48 | display: block; 49 | } 50 | } 51 | 52 | @each $breakpoint in $sizes { 53 | $query: nth($breakpoint, 1); 54 | $bpWidth: nth($breakpoint, 2); 55 | $bpHeight: nth($breakpoint, 3); 56 | 57 | @media only screen and #{$query} { 58 | width: $bpWidth; 59 | height: $bpHeight; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-magnifier", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "core-js": "^3.6.5", 11 | "vue": "^2.6.11" 12 | }, 13 | "devDependencies": { 14 | "@vue/cli-plugin-babel": "~4.5.0", 15 | "@vue/cli-service": "~4.5.0", 16 | "vue-template-compiler": "^2.6.11", 17 | "sass-loader": "^10.0.2", 18 | "node-sass": "^4.14.1" 19 | }, 20 | "browserslist": [ 21 | "> 1%", 22 | "last 2 versions", 23 | "not dead" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/vue-magnifier.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 149 | 150 | 216 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(App), 8 | }).$mount('#app') 9 | --------------------------------------------------------------------------------