├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── cjs ├── index.js └── package.json ├── es.js ├── esm └── index.js ├── index.js ├── min.js ├── package.json ├── rollup ├── babel.config.js └── es.config.js └── test ├── background.jpg ├── background.preview.jpg ├── desktop.css ├── desktop.html ├── index.html ├── index.js ├── package.json └── with-preview.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | node_modules/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | .travis.yml 4 | node_modules/ 5 | rollup/ 6 | test/ 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # with-preview 2 | 3 | ![frozen beach](./test/with-preview.jpg) 4 | 5 | **Photo by [Pierre Bouillot](https://unsplash.com/@pbouillot) on [Unsplash](https://unsplash.com/)** 6 | 7 | A built-in Custom Element based on _IntersectionObserver_ able to fade in good quality jpg over their preview version. 8 | 9 | It fallbacks all the way down to IE 9, simply replacing on the fly the preview source with its good quality counter-part. 10 | 11 | **[Live demo](https://webreflection.github.io/with-preview/test/)** 12 | 13 | 14 | ### Example 15 | 16 | ```html 17 | 18 | 19 | ``` 20 | 21 | Images will drop the `.preview` part of the _src_ and will fade in the non `.preview` version of the same image once the document shows the preview, and after the non-preview version is loaded. 22 | 23 | Previews can be manually generated or based on [µcompress](https://github.com/WebReflection/ucompress#readme) or [µcdn](https://github.com/WebReflection/ucdn#readme) previews. 24 | 25 | ```sh 26 | # example: will create test.preview.jpg 27 | npx ucompress --preview --source test.jpg 28 | ``` 29 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*! (c) Andrea Giammarchi @webreflection */ 3 | const css = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('ustyler')); 4 | 5 | const {customElements, getComputedStyle, IntersectionObserver} = window; 6 | const className = 'with-preview'; 7 | 8 | if (!customElements.get(className)) { 9 | const updateSrc = target => { 10 | target.src = target.src.replace(/\.preview(\.jpe?g(?:\?.*)?)$/, '$1'); 11 | }; 12 | let onConnected = updateSrc; 13 | if (IntersectionObserver) { 14 | const once = {once: true}; 15 | const onload = ({target}) => { 16 | target.addEventListener('transitionend', onTransitionEnd, once); 17 | target.style.opacity = 1; 18 | }; 19 | const onHeight = ({target}) => { 20 | const {parentElement} = target; 21 | if (parentElement.tagName.toLowerCase() === className) { 22 | const {width, height} = getComputedStyle(target); 23 | parentElement.style.cssText += 24 | ';width:' + width + 25 | ';height:' + height 26 | ; 27 | observer.observe(target.nextSibling); 28 | } 29 | }; 30 | const onTransitionEnd = ({target}) => { 31 | const {parentElement} = target; 32 | parentElement.parentElement.replaceChild(target, parentElement); 33 | }; 34 | const observer = new IntersectionObserver( 35 | entries => { 36 | for (let i = 0, {length} = entries; i < length; i++) { 37 | const {isIntersecting, target} = entries[i]; 38 | if (isIntersecting) { 39 | observer.unobserve(target); 40 | target.addEventListener('load', onload, once); 41 | updateSrc(target); 42 | } 43 | } 44 | }, 45 | { 46 | threshold: .2 47 | } 48 | ); 49 | onConnected = target => { 50 | if (!target.dataset.preview) { 51 | target.dataset.preview = 1; 52 | const { 53 | marginTop, 54 | marginRight, 55 | marginBottom, 56 | marginLeft 57 | } = getComputedStyle(target); 58 | const {height, parentElement} = target; 59 | const container = document.createElement(className); 60 | const clone = target.cloneNode(true); 61 | container.style.cssText = 62 | ';margin-top:' + marginTop + 63 | ';margin-right:' + marginRight + 64 | ';margin-bottom:' + marginBottom + 65 | ';margin-left:' + marginLeft 66 | ; 67 | parentElement.replaceChild(container, target); 68 | container.appendChild(target); 69 | container.appendChild(clone); 70 | if (height) 71 | onHeight({target}); 72 | else 73 | target.addEventListener('load', onHeight, once); 74 | } 75 | }; 76 | css( 77 | className + '{position:relative;display:inline-block;padding:0;}' + 78 | className + '>img{position:absolute;top:0;left:0;margin:0;}' + 79 | className + '>img:last-child{opacity:0;transition:opacity 1s ease-in;will-change:opacity;}' 80 | ); 81 | } 82 | customElements.define( 83 | className, 84 | class extends HTMLImageElement { 85 | connectedCallback() { 86 | onConnected(this); 87 | } 88 | }, 89 | {extends: 'img'} 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /cjs/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /es.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict"; 2 | /*! (c) Andrea Giammarchi @webreflection */ 3 | const{customElements:e,getComputedStyle:t,IntersectionObserver:n}=window,i="with-preview";if(!e.get(i)){const a=e=>{e.src=e.src.replace(/\.preview(\.jpe?g(?:\?.*)?)$/,"$1")};let o=a;if(n){const e={once:!0},s=({target:t})=>{t.addEventListener("transitionend",l,e),t.style.opacity=1},r=({target:e})=>{const{parentElement:n}=e;if(n.tagName.toLowerCase()===i){const{width:i,height:a}=t(e);n.style.cssText+=";width:"+i+";height:"+a,c.observe(e.nextSibling)}},l=({target:e})=>{const{parentElement:t}=e;t.parentElement.replaceChild(e,t)},c=new n(t=>{for(let n=0,{length:i}=t;n{if(!n.dataset.preview){n.dataset.preview=1;const{marginTop:a,marginRight:o,marginBottom:s,marginLeft:l}=t(n),{height:c,parentElement:d}=n,p=document.createElement(i),g=n.cloneNode(!0);p.style.cssText=";margin-top:"+a+";margin-right:"+o+";margin-bottom:"+s+";margin-left:"+l,d.replaceChild(p,n),p.appendChild(n),p.appendChild(g),c?r({target:n}):n.addEventListener("load",r,e)}},function(e){const t="string"==typeof e?[e]:[e[0]];for(let n=1,{length:i}=arguments;nimg{position:absolute;top:0;left:0;margin:0;}"+i+">img:last-child{opacity:0;transition:opacity 1s ease-in;will-change:opacity;}")}e.define(i,class extends HTMLImageElement{connectedCallback(){o(this)}},{extends:"img"})}}(); 4 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi @webreflection */ 2 | import css from 'ustyler'; 3 | 4 | const {customElements, getComputedStyle, IntersectionObserver} = window; 5 | const className = 'with-preview'; 6 | 7 | if (!customElements.get(className)) { 8 | const updateSrc = target => { 9 | target.src = target.src.replace(/\.preview(\.jpe?g(?:\?.*)?)$/, '$1'); 10 | }; 11 | let onConnected = updateSrc; 12 | if (IntersectionObserver) { 13 | const once = {once: true}; 14 | const onload = ({target}) => { 15 | target.addEventListener('transitionend', onTransitionEnd, once); 16 | target.style.opacity = 1; 17 | }; 18 | const onHeight = ({target}) => { 19 | const {parentElement} = target; 20 | if (parentElement.tagName.toLowerCase() === className) { 21 | const {width, height} = getComputedStyle(target); 22 | parentElement.style.cssText += 23 | ';width:' + width + 24 | ';height:' + height 25 | ; 26 | observer.observe(target.nextSibling); 27 | } 28 | }; 29 | const onTransitionEnd = ({target}) => { 30 | const {parentElement} = target; 31 | parentElement.parentElement.replaceChild(target, parentElement); 32 | }; 33 | const observer = new IntersectionObserver( 34 | entries => { 35 | for (let i = 0, {length} = entries; i < length; i++) { 36 | const {isIntersecting, target} = entries[i]; 37 | if (isIntersecting) { 38 | observer.unobserve(target); 39 | target.addEventListener('load', onload, once); 40 | updateSrc(target); 41 | } 42 | } 43 | }, 44 | { 45 | threshold: .2 46 | } 47 | ); 48 | onConnected = target => { 49 | if (!target.dataset.preview) { 50 | target.dataset.preview = 1; 51 | const { 52 | marginTop, 53 | marginRight, 54 | marginBottom, 55 | marginLeft 56 | } = getComputedStyle(target); 57 | const {height, parentElement} = target; 58 | const container = document.createElement(className); 59 | const clone = target.cloneNode(true); 60 | container.style.cssText = 61 | ';margin-top:' + marginTop + 62 | ';margin-right:' + marginRight + 63 | ';margin-bottom:' + marginBottom + 64 | ';margin-left:' + marginLeft 65 | ; 66 | parentElement.replaceChild(container, target); 67 | container.appendChild(target); 68 | container.appendChild(clone); 69 | if (height) 70 | onHeight({target}); 71 | else 72 | target.addEventListener('load', onHeight, once); 73 | } 74 | }; 75 | css( 76 | className + '{position:relative;display:inline-block;padding:0;}' + 77 | className + '>img{position:absolute;top:0;left:0;margin:0;}' + 78 | className + '>img:last-child{opacity:0;transition:opacity 1s ease-in;will-change:opacity;}' 79 | ); 80 | } 81 | customElements.define( 82 | className, 83 | class extends HTMLImageElement { 84 | connectedCallback() { 85 | onConnected(this); 86 | } 87 | }, 88 | {extends: 'img'} 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function _classCallCheck(instance, Constructor) { 5 | if (!(instance instanceof Constructor)) { 6 | throw new TypeError("Cannot call a class as a function"); 7 | } 8 | } 9 | 10 | function _defineProperties(target, props) { 11 | for (var i = 0; i < props.length; i++) { 12 | var descriptor = props[i]; 13 | descriptor.enumerable = descriptor.enumerable || false; 14 | descriptor.configurable = true; 15 | if ("value" in descriptor) descriptor.writable = true; 16 | Object.defineProperty(target, descriptor.key, descriptor); 17 | } 18 | } 19 | 20 | function _createClass(Constructor, protoProps, staticProps) { 21 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 22 | if (staticProps) _defineProperties(Constructor, staticProps); 23 | return Constructor; 24 | } 25 | 26 | function _inherits(subClass, superClass) { 27 | if (typeof superClass !== "function" && superClass !== null) { 28 | throw new TypeError("Super expression must either be null or a function"); 29 | } 30 | 31 | subClass.prototype = Object.create(superClass && superClass.prototype, { 32 | constructor: { 33 | value: subClass, 34 | writable: true, 35 | configurable: true 36 | } 37 | }); 38 | if (superClass) _setPrototypeOf(subClass, superClass); 39 | } 40 | 41 | function _getPrototypeOf(o) { 42 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 43 | return o.__proto__ || Object.getPrototypeOf(o); 44 | }; 45 | return _getPrototypeOf(o); 46 | } 47 | 48 | function _setPrototypeOf(o, p) { 49 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 50 | o.__proto__ = p; 51 | return o; 52 | }; 53 | 54 | return _setPrototypeOf(o, p); 55 | } 56 | 57 | function _isNativeReflectConstruct() { 58 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 59 | if (Reflect.construct.sham) return false; 60 | if (typeof Proxy === "function") return true; 61 | 62 | try { 63 | Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 64 | return true; 65 | } catch (e) { 66 | return false; 67 | } 68 | } 69 | 70 | function _construct(Parent, args, Class) { 71 | if (_isNativeReflectConstruct()) { 72 | _construct = Reflect.construct; 73 | } else { 74 | _construct = function _construct(Parent, args, Class) { 75 | var a = [null]; 76 | a.push.apply(a, args); 77 | var Constructor = Function.bind.apply(Parent, a); 78 | var instance = new Constructor(); 79 | if (Class) _setPrototypeOf(instance, Class.prototype); 80 | return instance; 81 | }; 82 | } 83 | 84 | return _construct.apply(null, arguments); 85 | } 86 | 87 | function _isNativeFunction(fn) { 88 | return Function.toString.call(fn).indexOf("[native code]") !== -1; 89 | } 90 | 91 | function _wrapNativeSuper(Class) { 92 | var _cache = typeof Map === "function" ? new Map() : undefined; 93 | 94 | _wrapNativeSuper = function _wrapNativeSuper(Class) { 95 | if (Class === null || !_isNativeFunction(Class)) return Class; 96 | 97 | if (typeof Class !== "function") { 98 | throw new TypeError("Super expression must either be null or a function"); 99 | } 100 | 101 | if (typeof _cache !== "undefined") { 102 | if (_cache.has(Class)) return _cache.get(Class); 103 | 104 | _cache.set(Class, Wrapper); 105 | } 106 | 107 | function Wrapper() { 108 | return _construct(Class, arguments, _getPrototypeOf(this).constructor); 109 | } 110 | 111 | Wrapper.prototype = Object.create(Class.prototype, { 112 | constructor: { 113 | value: Wrapper, 114 | enumerable: false, 115 | writable: true, 116 | configurable: true 117 | } 118 | }); 119 | return _setPrototypeOf(Wrapper, Class); 120 | }; 121 | 122 | return _wrapNativeSuper(Class); 123 | } 124 | 125 | function _assertThisInitialized(self) { 126 | if (self === void 0) { 127 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 128 | } 129 | 130 | return self; 131 | } 132 | 133 | function _possibleConstructorReturn(self, call) { 134 | if (call && (typeof call === "object" || typeof call === "function")) { 135 | return call; 136 | } 137 | 138 | return _assertThisInitialized(self); 139 | } 140 | 141 | function _createSuper(Derived) { 142 | var hasNativeReflectConstruct = _isNativeReflectConstruct(); 143 | 144 | return function () { 145 | var Super = _getPrototypeOf(Derived), 146 | result; 147 | 148 | if (hasNativeReflectConstruct) { 149 | var NewTarget = _getPrototypeOf(this).constructor; 150 | 151 | result = Reflect.construct(Super, arguments, NewTarget); 152 | } else { 153 | result = Super.apply(this, arguments); 154 | } 155 | 156 | return _possibleConstructorReturn(this, result); 157 | }; 158 | } 159 | 160 | /** 161 | * Create, append, and return, a style node with the passed CSS content. 162 | * @param {string|string[]} template the CSS text or a template literal array. 163 | * @param {...any} values the template literal interpolations. 164 | * @return {HTMLStyleElement} the node appended as head last child. 165 | */ 166 | function ustyler(template) { 167 | var text = typeof template == 'string' ? [template] : [template[0]]; 168 | 169 | for (var i = 1, length = arguments.length; i < length; i++) { 170 | text.push(arguments[i], template[i]); 171 | } 172 | 173 | var style = document.createElement('style'); 174 | style.type = 'text/css'; 175 | style.appendChild(document.createTextNode(text.join(''))); 176 | return document.head.appendChild(style); 177 | } 178 | 179 | var _window = window, 180 | customElements = _window.customElements, 181 | getComputedStyle = _window.getComputedStyle, 182 | IntersectionObserver = _window.IntersectionObserver; 183 | var className = 'with-preview'; 184 | 185 | if (!customElements.get(className)) { 186 | var updateSrc = function updateSrc(target) { 187 | target.src = target.src.replace(/\.preview(\.jpe?g(?:\?.*)?)$/, '$1'); 188 | }; 189 | 190 | var onConnected = updateSrc; 191 | 192 | if (IntersectionObserver) { 193 | var once = { 194 | once: true 195 | }; 196 | 197 | var onload = function onload(_ref) { 198 | var target = _ref.target; 199 | target.addEventListener('transitionend', onTransitionEnd, once); 200 | target.style.opacity = 1; 201 | }; 202 | 203 | var onHeight = function onHeight(_ref2) { 204 | var target = _ref2.target; 205 | var parentElement = target.parentElement; 206 | 207 | if (parentElement.tagName.toLowerCase() === className) { 208 | var _getComputedStyle = getComputedStyle(target), 209 | width = _getComputedStyle.width, 210 | height = _getComputedStyle.height; 211 | 212 | parentElement.style.cssText += ';width:' + width + ';height:' + height; 213 | observer.observe(target.nextSibling); 214 | } 215 | }; 216 | 217 | var onTransitionEnd = function onTransitionEnd(_ref3) { 218 | var target = _ref3.target; 219 | var parentElement = target.parentElement; 220 | parentElement.parentElement.replaceChild(target, parentElement); 221 | }; 222 | 223 | var observer = new IntersectionObserver(function (entries) { 224 | for (var i = 0, length = entries.length; i < length; i++) { 225 | var _entries$i = entries[i], 226 | isIntersecting = _entries$i.isIntersecting, 227 | target = _entries$i.target; 228 | 229 | if (isIntersecting) { 230 | observer.unobserve(target); 231 | target.addEventListener('load', onload, once); 232 | updateSrc(target); 233 | } 234 | } 235 | }, { 236 | threshold: .2 237 | }); 238 | 239 | onConnected = function onConnected(target) { 240 | if (!target.dataset.preview) { 241 | target.dataset.preview = 1; 242 | 243 | var _getComputedStyle2 = getComputedStyle(target), 244 | marginTop = _getComputedStyle2.marginTop, 245 | marginRight = _getComputedStyle2.marginRight, 246 | marginBottom = _getComputedStyle2.marginBottom, 247 | marginLeft = _getComputedStyle2.marginLeft; 248 | 249 | var height = target.height, 250 | parentElement = target.parentElement; 251 | var container = document.createElement(className); 252 | var clone = target.cloneNode(true); 253 | container.style.cssText = ';margin-top:' + marginTop + ';margin-right:' + marginRight + ';margin-bottom:' + marginBottom + ';margin-left:' + marginLeft; 254 | parentElement.replaceChild(container, target); 255 | container.appendChild(target); 256 | container.appendChild(clone); 257 | if (height) onHeight({ 258 | target: target 259 | });else target.addEventListener('load', onHeight, once); 260 | } 261 | }; 262 | 263 | ustyler(className + '{position:relative;display:inline-block;padding:0;}' + className + '>img{position:absolute;top:0;left:0;margin:0;}' + className + '>img:last-child{opacity:0;transition:opacity 1s ease-in;will-change:opacity;}'); 264 | } 265 | 266 | customElements.define(className, /*#__PURE__*/function (_HTMLImageElement) { 267 | _inherits(_class, _HTMLImageElement); 268 | 269 | var _super = _createSuper(_class); 270 | 271 | function _class() { 272 | _classCallCheck(this, _class); 273 | 274 | return _super.apply(this, arguments); 275 | } 276 | 277 | _createClass(_class, [{ 278 | key: "connectedCallback", 279 | value: function connectedCallback() { 280 | onConnected(this); 281 | } 282 | }]); 283 | 284 | return _class; 285 | }( /*#__PURE__*/_wrapNativeSuper(HTMLImageElement)), { 286 | "extends": 'img' 287 | }); 288 | } 289 | 290 | }()); 291 | -------------------------------------------------------------------------------- /min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function c(t,e){for(var n=0;nimg{position:absolute;top:0;left:0;margin:0;}"+b+">img:last-child{opacity:0;transition:opacity 1s ease-in;will-change:opacity;}")),e.define(b,function(){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&l(t,e)}(a,f(HTMLImageElement));var n,r,t,e,o,i=(n=a,r=p(),function(){var t,e=u(n);return s(this,r?(t=u(this).constructor,Reflect.construct(e,arguments,t)):e.apply(this,arguments))});function a(){return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,a),i.apply(this,arguments)}return t=a,(e=[{key:"connectedCallback",value:function(){m(this)}}])&&c(t.prototype,e),o&&c(t,o),a}(),{extends:"img"}))}(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-preview", 3 | "version": "0.1.5", 4 | "description": "An built-in extend that loads its non-preview counterpart once visible", 5 | "main": "./cjs/index.js", 6 | "scripts": { 7 | "build": "npm run cjs && npm run rollup:es && npm run rollup:babel && npm run min && npm run fix:default && npm run test", 8 | "cjs": "ascjs --no-default esm cjs", 9 | "rollup:es": "rollup --config rollup/es.config.js", 10 | "rollup:babel": "rollup --config rollup/babel.config.js", 11 | "min": "uglifyjs index.js --support-ie8 --comments=/^!/ -c -m -o min.js", 12 | "fix:default": "sed -i 's/({})/({}).default/' index.js && sed -i 's/({})/({}).default/' es.js && sed -i 's/({})/({}).default/' min.js" 13 | }, 14 | "keywords": [ 15 | "img", 16 | "lazy", 17 | "preview" 18 | ], 19 | "author": "Andrea Giammarchi", 20 | "license": "ISC", 21 | "devDependencies": { 22 | "@babel/core": "^7.9.6", 23 | "@babel/preset-env": "^7.9.6", 24 | "ascjs": "^4.0.0", 25 | "rollup": "^2.7.6", 26 | "rollup-plugin-babel": "^4.4.0", 27 | "rollup-plugin-node-resolve": "^5.2.0", 28 | "rollup-plugin-terser": "^5.3.0", 29 | "uglify-js": "^3.9.1" 30 | }, 31 | "module": "./esm/index.js", 32 | "type": "module", 33 | "exports": { 34 | "import": "./esm/index.js", 35 | "default": "./cjs/index.js" 36 | }, 37 | "unpkg": "min.js", 38 | "dependencies": { 39 | "ustyler": "^1.0.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rollup/babel.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import babel from 'rollup-plugin-babel'; 3 | 4 | export default { 5 | input: './esm/index.js', 6 | plugins: [ 7 | 8 | resolve({module: true}), 9 | babel({presets: ['@babel/preset-env']}) 10 | ], 11 | 12 | output: { 13 | exports: 'named', 14 | file: './index.js', 15 | format: 'iife', 16 | name: 'imgPreview' 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /rollup/es.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import {terser} from 'rollup-plugin-terser'; 3 | 4 | export default { 5 | input: './esm/index.js', 6 | plugins: [ 7 | 8 | resolve({module: true}), 9 | terser() 10 | ], 11 | 12 | output: { 13 | exports: 'named', 14 | file: './es.js', 15 | format: 'iife', 16 | name: 'imgPreview' 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /test/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/with-preview/525c6710d2b145482bd544bb335ede3174468978/test/background.jpg -------------------------------------------------------------------------------- /test/background.preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/with-preview/525c6710d2b145482bd544bb335ede3174468978/test/background.preview.jpg -------------------------------------------------------------------------------- /test/desktop.css: -------------------------------------------------------------------------------- 1 | *, *::before, *::after { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | padding: 0; 7 | margin: 0; 8 | } 9 | 10 | html { 11 | font-family: sans-serif; 12 | background-image: url(background.jpg); 13 | background-size: cover; 14 | background-repeat: no-repeat; 15 | min-height: 100vh; 16 | min-height: -webkit-fill-available; 17 | } 18 | 19 | footer { 20 | --height: 48px; 21 | --border: 2px; 22 | --space: 8px; 23 | display: flex; 24 | align-items: center; 25 | position: fixed; 26 | bottom: 0; 27 | height: var(--height); 28 | width: 100vw; 29 | color: #fff; 30 | border-top: var(--border) solid black; 31 | } 32 | 33 | footer > button { 34 | color: inherit; 35 | z-index: 1; 36 | border: 0; 37 | background: transparent; 38 | padding: var(--space); 39 | margin: auto var(--space); 40 | font-weight: bold; 41 | text-transform: uppercase; 42 | } 43 | 44 | footer > .menu { 45 | overflow: hidden; 46 | z-index: 0; 47 | position: fixed; 48 | bottom: calc(var(--height) - var(--border)); 49 | left: 0; 50 | height: 0; 51 | padding: 0; 52 | width: 40vw; 53 | border-top: var(--border) solid black; 54 | transition: height .5s ease-out; 55 | } 56 | 57 | footer::before, footer > .menu::before { 58 | z-index: -1; 59 | position: absolute; 60 | display: block; 61 | opacity: 0.96; 62 | content: ""; 63 | width: 100%; 64 | height: 100%; 65 | background-image: url(background.preview.jpg); 66 | background-size: cover; 67 | background-repeat: no-repeat; 68 | background-attachment: fixed; 69 | } 70 | 71 | footer > .menu > * { 72 | margin: var(--space); 73 | } 74 | 75 | html:not([dir="rtl"]) footer > .menu { 76 | border-right: var(--border) solid black; 77 | border-top-right-radius: 8px; 78 | } 79 | 80 | html[dir="rtl"] footer > .menu { 81 | border-left: var(--border) solid black; 82 | } 83 | 84 | footer > .menu.visible { 85 | height: 60vh; 86 | } 87 | -------------------------------------------------------------------------------- /test/desktop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Desktop Example 18 | 19 | 20 | 21 |
22 |
23 |
24 | 27 | 28 |
29 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | img with-preview Custom Element built-in extends 7 | 14 | 22 | 23 | 24 |

Picture by Pierre Bouillot on Unsplash

25 |
26 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vestibulum, tortor ac laoreet suscipit, justo metus suscipit arcu, eu posuere leo velit vitae enim. Sed mattis ornare nisi, id accumsan erat pretium et. Suspendisse potenti. Integer in convallis elit. Vestibulum tempus laoreet erat et vulputate. Sed nec purus mauris. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque tincidunt vulputate velit, pretium malesuada eros facilisis ultrices. Aliquam erat volutpat. Nunc fermentum malesuada turpis at laoreet. Aliquam in sagittis lorem. Praesent vestibulum metus eu neque auctor mollis non ac turpis. Cras quis ex ut massa dapibus efficitur et non velit. Nulla et semper arcu.

27 |

Such A Great Picture

28 | 29 |

Morbi eu nulla quis urna aliquam mollis nec quis tellus. Nulla at magna ac orci viverra volutpat eget sit amet tellus. Ut ut euismod turpis. Donec tincidunt sollicitudin mi sit amet faucibus. Suspendisse sit amet bibendum mauris. Suspendisse euismod aliquam neque vel mollis. Vestibulum aliquam enim ac lectus bibendum faucibus. Etiam pellentesque quis risus in malesuada. Nunc eleifend ut est sed elementum. Aliquam id porta odio. Etiam sodales tortor quis enim iaculis viverra. Nunc laoreet diam massa, in iaculis sapien varius egestas. Cras mollis mollis rutrum.

30 |

Nice with auto fade-in

31 | 32 |

Sed ut scelerisque nibh. Pellentesque at sapien viverra, vestibulum erat ornare, imperdiet risus. In vel ipsum vitae massa tempor blandit. Vivamus ac leo at lectus luctus interdum lacinia quis diam. Vestibulum laoreet orci aliquam congue hendrerit. Morbi ullamcorper purus mauris, non laoreet lorem tempus sed. Sed placerat suscipit augue. In hac habitasse platea dictumst. Proin sit amet malesuada erat. Vivamus tincidunt ex id aliquam vulputate. Nulla quis justo bibendum lacus elementum feugiat id et massa. Suspendisse potenti. Cras lacinia imperdiet consequat. Nullam consequat tellus quis dolor fringilla aliquam. Donec a urna est.

33 |

All via a single Custom Element

34 | 35 |

Phasellus sed bibendum velit. Fusce efficitur ante nec leo scelerisque ullamcorper. Pellentesque interdum nisi ac tellus dapibus luctus. Nulla convallis tempus massa, ac viverra erat. Sed quis tellus in felis finibus sollicitudin. Proin mi sapien, dignissim ac quam in, pulvinar viverra nisl. Ut accumsan nibh eu mi ultrices sodales. Etiam vestibulum faucibus urna id pharetra. Vivamus commodo turpis nec nisl aliquam, sit amet aliquet odio consectetur. Nunc finibus a magna eu tempus.

36 |

It's great

37 | 38 |

Phasellus vitae risus porta, mollis lectus eu, accumsan purus. Nam sollicitudin leo magna, id lobortis ligula tristique ut. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus nunc magna, sagittis eget diam ac, malesuada accumsan velit. Vestibulum sed sapien gravida, vehicula nulla eget, vulputate sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vel est lacus. Maecenas pulvinar urna non tempus ultrices. Mauris ut libero ac purus euismod feugiat.

39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('../cjs'); -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /test/with-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/with-preview/525c6710d2b145482bd544bb335ede3174468978/test/with-preview.jpg --------------------------------------------------------------------------------