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 |
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;n