├── .eslintrc.json
├── .gitignore
├── demo-preview.gif
├── example
├── test.jpg
├── main.js
└── App.vue
├── src
├── index.js
└── ExpandableImage.vue
├── LICENSE
├── package.json
├── README.md
└── dist
├── vue-expandable-image.min.js
└── vue-expandable-image.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {}
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
--------------------------------------------------------------------------------
/demo-preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TahaSh/vue-expandable-image/HEAD/demo-preview.gif
--------------------------------------------------------------------------------
/example/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TahaSh/vue-expandable-image/HEAD/example/test.jpg
--------------------------------------------------------------------------------
/example/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App'
3 |
4 | import VueExpandableImage from '../src'
5 | Vue.use(VueExpandableImage)
6 |
7 | new Vue({
8 | render: h => h(App)
9 | }).$mount('#app')
10 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import ExpandableImage from './ExpandableImage.vue'
2 | let vueExpandableImage = {}
3 |
4 | vueExpandableImage.install = function (Vue) {
5 | Vue.component('expandable-image', ExpandableImage)
6 | }
7 |
8 | if (typeof window !== 'undefined' && window.Vue) {
9 | window.Vue.use(vueExpandableImage)
10 | }
11 |
12 | export default vueExpandableImage
13 |
--------------------------------------------------------------------------------
/example/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
14 |
15 |
16 |
17 |
27 |
28 |
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2019 Taha Shashtari
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-expandable-image",
3 | "version": "0.1.0",
4 | "main": "dist/vue-expandable-image.js",
5 | "description": "A wrapper component for images to make them open in fullscreen.",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/TahaSh/vue-expandable-image"
10 | },
11 | "bugs": "https://github.com/TahaSh/vue-expandable-image/issues",
12 | "devDependencies": {
13 | "@vue/cli": "^3.3.0",
14 | "@vue/cli-service": "^3.3.0",
15 | "rollup": "^1.1.2",
16 | "rollup-plugin-buble": "^0.19.6",
17 | "rollup-plugin-commonjs": "^9.2.0",
18 | "rollup-plugin-node-resolve": "^4.0.0",
19 | "rollup-plugin-replace": "^2.1.0",
20 | "rollup-plugin-vue": "^4.7.2",
21 | "uglify-js": "^3.4.9",
22 | "vue": "^2.5.22",
23 | "vue-template-compiler": "^2.5.22",
24 | "webpack": "^4.29.0"
25 | },
26 | "scripts": {
27 | "build": "rollup --environment NODE_ENV:production -c build/rollup.config.js && uglifyjs dist/vue-expandable-image.js -cm --comments -o dist/vue-expandable-image.min.js",
28 | "dev": "vue-cli-service serve example/main.js"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-expandable-image
2 |
3 | > Allows your images to open in full size.
4 |
5 | # Demo Preview
6 |
7 | 
8 |
9 | ## Setup
10 |
11 | ```
12 | npm install vue-expandable-image
13 | ```
14 |
15 | You have two ways to setup `vue-expandable-image`:
16 |
17 | #### CommonJS (Webpack/Browserify)
18 |
19 | - ES6
20 |
21 | ```js
22 | import VueExpandableImage from 'vue-expandable-image'
23 | Vue.use(VueExpandableImage)
24 | ```
25 |
26 | - ES5
27 |
28 | ```js
29 | var VueExpandableImage = require('vue-expandable-image')
30 | Vue.use(VueExpandableImage)
31 | ```
32 |
33 | #### Include
34 |
35 | Include it directly with a `
122 |
123 |
200 |
--------------------------------------------------------------------------------
/dist/vue-expandable-image.js:
--------------------------------------------------------------------------------
1 | /**
2 | * vue-expandable-image v0.1.0
3 | * (c) 2019 Taha Shashtari
4 | * @license MIT
5 | */
6 | (function (global, factory) {
7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8 | typeof define === 'function' && define.amd ? define(factory) :
9 | (global = global || self, global.VueExpandableImage = factory());
10 | }(this, function () { 'use strict';
11 |
12 | //
13 | //
14 | //
15 | //
16 | //
17 | //
18 | //
19 | //
20 | //
21 | //
22 | //
23 | //
24 | //
25 | //
26 | //
27 | //
28 | //
29 | //
30 | //
31 | //
32 | //
33 | //
34 | //
35 | //
36 | //
37 | //
38 | //
39 | //
40 |
41 | var script = {
42 | props: {
43 | closeOnBackgroundClick: {
44 | type: Boolean,
45 | default: false
46 | }
47 | },
48 |
49 | data: function data () {
50 | return {
51 | expanded: false,
52 | closeButtonRef: null
53 | }
54 | },
55 |
56 | methods: {
57 | closeImage: function closeImage (event) {
58 | this.expanded = false;
59 | event.stopPropagation();
60 | },
61 |
62 | freezeVp: function freezeVp (e) {
63 | e.preventDefault();
64 | },
65 |
66 | onExpandedImageClick: function onExpandedImageClick (e) {
67 | e.stopPropagation();
68 | var image = this.cloned.querySelector('img');
69 | var imagePosition = this.getRenderedSize(image.width, image.height, image.naturalWidth, image.naturalHeight);
70 | if (
71 | (e.clientX < imagePosition.left) ||
72 | (e.clientX > imagePosition.right) ||
73 | (e.clientY < imagePosition.top) ||
74 | (e.clientY > imagePosition.bottom)
75 | ) {
76 | this.expanded = false;
77 | }
78 | },
79 |
80 | getRenderedSize: function getRenderedSize (cWidth, cHeight, oWidth, oHeight) {
81 | var oRatio = oWidth > oHeight
82 | ? oWidth / oHeight
83 | : oHeight / oWidth;
84 | var width = oWidth >= oHeight
85 | ? oRatio * cHeight
86 | : cWidth;
87 | var height = oHeight > oWidth
88 | ? oRatio * cWidth
89 | : cHeight;
90 | var left = (this.cloned.clientWidth - width) / 2;
91 | var right = left + width;
92 | var top = (this.cloned.clientHeight - height) / 2;
93 | var bottom = top + height;
94 | return { left: left, top: top, right: right, bottom: bottom }
95 | }
96 | },
97 |
98 | watch: {
99 | expanded: function expanded (status) {
100 | var this$1 = this;
101 |
102 | this.$nextTick(function () {
103 | if (status) {
104 | this$1.cloned = this$1.$el.cloneNode(true);
105 | this$1.closeButtonRef = this$1.cloned.querySelector('.close-button');
106 | this$1.closeButtonRef.addEventListener('click', this$1.closeImage);
107 | document.body.appendChild(this$1.cloned);
108 | document.body.style.overflow = 'hidden';
109 | this$1.cloned.addEventListener('touchmove', this$1.freezeVp, false);
110 | if (this$1.closeOnBackgroundClick) {
111 | this$1.cloned.addEventListener('click', this$1.onExpandedImageClick);
112 | }
113 | setTimeout(function () {
114 | this$1.cloned.style.opacity = 1;
115 | }, 0);
116 | } else {
117 | this$1.cloned.style.opacity = 0;
118 | this$1.cloned.removeEventListener('touchmove', this$1.freezeVp, false);
119 | if (this$1.closeOnBackgroundClick) {
120 | this$1.cloned.removeEventListener('click', this$1.onExpandedImageClick);
121 | }
122 | setTimeout(function () {
123 | this$1.closeButtonRef.removeEventListener('click', this$1.closeImage);
124 | this$1.cloned.remove();
125 | this$1.cloned = null;
126 | this$1.closeButtonRef = null;
127 | document.body.style.overflow = 'auto';
128 | }, 250);
129 | }
130 | });
131 | }
132 | }
133 | };
134 |
135 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier
136 | /* server only */
137 | , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
138 | if (typeof shadowMode !== 'boolean') {
139 | createInjectorSSR = createInjector;
140 | createInjector = shadowMode;
141 | shadowMode = false;
142 | } // Vue.extend constructor export interop.
143 |
144 |
145 | var options = typeof script === 'function' ? script.options : script; // render functions
146 |
147 | if (template && template.render) {
148 | options.render = template.render;
149 | options.staticRenderFns = template.staticRenderFns;
150 | options._compiled = true; // functional template
151 |
152 | if (isFunctionalTemplate) {
153 | options.functional = true;
154 | }
155 | } // scopedId
156 |
157 |
158 | if (scopeId) {
159 | options._scopeId = scopeId;
160 | }
161 |
162 | var hook;
163 |
164 | if (moduleIdentifier) {
165 | // server build
166 | hook = function hook(context) {
167 | // 2.3 injection
168 | context = context || // cached call
169 | this.$vnode && this.$vnode.ssrContext || // stateful
170 | this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional
171 | // 2.2 with runInNewContext: true
172 |
173 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
174 | context = __VUE_SSR_CONTEXT__;
175 | } // inject component styles
176 |
177 |
178 | if (style) {
179 | style.call(this, createInjectorSSR(context));
180 | } // register component module identifier for async chunk inference
181 |
182 |
183 | if (context && context._registeredComponents) {
184 | context._registeredComponents.add(moduleIdentifier);
185 | }
186 | }; // used by ssr in case component is cached and beforeCreate
187 | // never gets called
188 |
189 |
190 | options._ssrRegister = hook;
191 | } else if (style) {
192 | hook = shadowMode ? function () {
193 | style.call(this, createInjectorShadow(this.$root.$options.shadowRoot));
194 | } : function (context) {
195 | style.call(this, createInjector(context));
196 | };
197 | }
198 |
199 | if (hook) {
200 | if (options.functional) {
201 | // register for functional component in vue file
202 | var originalRender = options.render;
203 |
204 | options.render = function renderWithStyleInjection(h, context) {
205 | hook.call(context);
206 | return originalRender(h, context);
207 | };
208 | } else {
209 | // inject component registration as beforeCreate hook
210 | var existing = options.beforeCreate;
211 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
212 | }
213 | }
214 |
215 | return script;
216 | }
217 |
218 | var normalizeComponent_1 = normalizeComponent;
219 |
220 | var isOldIE = typeof navigator !== 'undefined' && /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());
221 | function createInjector(context) {
222 | return function (id, style) {
223 | return addStyle(id, style);
224 | };
225 | }
226 | var HEAD = document.head || document.getElementsByTagName('head')[0];
227 | var styles = {};
228 |
229 | function addStyle(id, css) {
230 | var group = isOldIE ? css.media || 'default' : id;
231 | var style = styles[group] || (styles[group] = {
232 | ids: new Set(),
233 | styles: []
234 | });
235 |
236 | if (!style.ids.has(id)) {
237 | style.ids.add(id);
238 | var code = css.source;
239 |
240 | if (css.map) {
241 | // https://developer.chrome.com/devtools/docs/javascript-debugging
242 | // this makes source maps inside style tags work properly in Chrome
243 | code += '\n/*# sourceURL=' + css.map.sources[0] + ' */'; // http://stackoverflow.com/a/26603875
244 |
245 | code += '\n/*# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) + ' */';
246 | }
247 |
248 | if (!style.element) {
249 | style.element = document.createElement('style');
250 | style.element.type = 'text/css';
251 | if (css.media) { style.element.setAttribute('media', css.media); }
252 | HEAD.appendChild(style.element);
253 | }
254 |
255 | if ('styleSheet' in style.element) {
256 | style.styles.push(code);
257 | style.element.styleSheet.cssText = style.styles.filter(Boolean).join('\n');
258 | } else {
259 | var index = style.ids.size - 1;
260 | var textNode = document.createTextNode(code);
261 | var nodes = style.element.childNodes;
262 | if (nodes[index]) { style.element.removeChild(nodes[index]); }
263 | if (nodes.length) { style.element.insertBefore(textNode, nodes[index]); }else { style.element.appendChild(textNode); }
264 | }
265 | }
266 | }
267 |
268 | var browser = createInjector;
269 |
270 | /* script */
271 | var __vue_script__ = script;
272 |
273 | /* template */
274 | var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"expandable-image",class:{
275 | expanded: _vm.expanded
276 | },on:{"click":function($event){_vm.expanded = true;}}},[(_vm.expanded)?_c('i',{staticClass:"close-button"},[_c('svg',{staticStyle:{"width":"24px","height":"24px"},attrs:{"viewBox":"0 0 24 24"}},[_c('path',{attrs:{"fill":"#666666","d":"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"}})])]):_vm._e(),_vm._v(" "),(!_vm.expanded)?_c('i',{staticClass:"expand-button"},[_c('svg',{staticStyle:{"width":"24px","height":"24px"},attrs:{"viewBox":"0 0 24 24"}},[_c('path',{attrs:{"fill":"#000000","d":"M10,21V19H6.41L10.91,14.5L9.5,13.09L5,17.59V14H3V21H10M14.5,10.91L19,6.41V10H21V3H14V5H17.59L13.09,9.5L14.5,10.91Z"}})])]):_vm._e(),_vm._v(" "),_c('img',_vm._b({},'img',_vm.$attrs,false))])};
277 | var __vue_staticRenderFns__ = [];
278 |
279 | /* style */
280 | var __vue_inject_styles__ = function (inject) {
281 | if (!inject) { return }
282 | inject("data-v-3c2a268c_0", { source: ".expandable-image{position:relative;transition:.25s opacity;cursor:zoom-in}body>.expandable-image.expanded{position:fixed;z-index:999999;top:0;left:0;width:100%;height:100%;background:#000;display:flex;align-items:center;opacity:0;padding-bottom:0!important;cursor:default}body>.expandable-image.expanded>img{width:100%;max-width:1200px;max-height:100%;object-fit:contain;margin:0 auto}body>.expandable-image.expanded>.close-button{display:block}.close-button{position:fixed;top:10px;right:10px;display:none;cursor:pointer}.close-button svg,.expand-button svg{filter:drop-shadow(1px 1px 1px rgba(0, 0, 0, .5))}.close-button svg path,.expand-button svg path{fill:#fff}.expand-button{position:absolute;z-index:999;right:10px;top:10px;padding:0;align-items:center;justify-content:center;padding:3px;opacity:0;transition:.2s opacity}.expandable-image:hover .expand-button{opacity:1}.expand-button svg{width:20px;height:20px}.expand-button path{fill:#fff}.expandable-image img{width:100%}", map: undefined, media: undefined });
283 |
284 | };
285 | /* scoped */
286 | var __vue_scope_id__ = undefined;
287 | /* module identifier */
288 | var __vue_module_identifier__ = undefined;
289 | /* functional template */
290 | var __vue_is_functional_template__ = false;
291 | /* style inject SSR */
292 |
293 |
294 |
295 | var ExpandableImage = normalizeComponent_1(
296 | { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
297 | __vue_inject_styles__,
298 | __vue_script__,
299 | __vue_scope_id__,
300 | __vue_is_functional_template__,
301 | __vue_module_identifier__,
302 | browser,
303 | undefined
304 | );
305 |
306 | var vueExpandableImage = {};
307 |
308 | vueExpandableImage.install = function (Vue) {
309 | Vue.component('expandable-image', ExpandableImage);
310 | };
311 |
312 | if (typeof window !== 'undefined' && window.Vue) {
313 | window.Vue.use(vueExpandableImage);
314 | }
315 |
316 | return vueExpandableImage;
317 |
318 | }));
319 |
--------------------------------------------------------------------------------