├── .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 |
2 |
3 |
7 |
8 |
9 |
10 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/vue-magnifier.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
13 |
14 |
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 |
--------------------------------------------------------------------------------