Permalink
Browse files

feat(b-img-lazy): Lazy loaded image component (#943)

* feat(b-img-lazy): Lazy load image component

Uses `b-img` as a base.

User can supply a `blank-src` image as a placeholder, or leave null and one will be generated automatically (with optional color set by `blank-color`).

`offset` controls how many pixels away from the view port before the image will be shown

Provides most of the same props as `b-img`

* Update index.js

* Update img-lazy.vue

* Update img-lazy.vue

* Update img-lazy.vue

* Update img-lazy.vue

* Update meta.json

* Update README.md

* Update README.md

* [img-lazy]: Add block prop

* make throttle time configurable via prop throttle

* Update README.md

* Update README.md

* Remove listeners on beforeDestroy, not destroyed hook
  • Loading branch information...
tmorehouse committed Aug 31, 2017
1 parent 4d823ce commit 68138cbdf0d3a2647e94cda073d6627e41de422c
Showing with 221 additions and 2 deletions.
  1. +47 −0 docs/components/img/README.md
  2. +3 −2 docs/components/img/meta.json
  3. +169 −0 lib/components/img-lazy.vue
  4. +2 −0 lib/components/index.js
@@ -140,3 +140,50 @@ The default `blank-color` is `transparent`.
- Blank images are rendered using SVG image data URLs.
- The `width` and `height` props will also apply the `width` and `height` attributes to the rendered `<img>` tag, even if `blank` is not set.
## Lazy Loaded images
> Use our complementary `<b-img-lazy>` image component (based on `<b-img>`) to lazy
load images as they are scrolled into view (or within `offset` pixels of the viewport).
### Usage
Set the `src` prop to the URL of the image you want loadied lazily, and either specify a
placeholder image URL via the prop `blank-src`, or have a blank placeholder image generated
for you by leaving `blank-src` as `null`.
Specify the width and height of the placeholder via the `blank-width` and `blank-height`
props. If these props are not set, then they will fall back to the `width` and `height`
props (which are applied to the image specified via `src`).
Control the generated blank image color by setting the prop `blank-color`.
Placeholder images (either explicity provided, or dynamicaly generated) should have the same
width and height values, or at least the same aspect ratio, as the `src` image.
Feel free to use the `fluid`, `fluid-grow`, `thumbnail`, and `rounded` props of `<b-img>`.
The `offset` prop specifies the number of pixels that an image needs to be near to
the viewport to trigger it to be shown. The default value is `360`.
The `throttle` prop controls how long (in ms) after a scroll (or resize or
orientationchange) event happens before checking if the image is has come within
view (or within `offset` of view). The default is `100` (ms).
Once an image has come into view and is shown, the scroll event listeners are
removed.
**Example usage:**
```html
<div>
<b-img-lazy src="https://lorempixel.com/600/400/cats/1/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
<b-img-lazy src="https://lorempixel.com/600/400/cats/3/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
<b-img-lazy src="https://lorempixel.com/600/400/cats/4/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
<b-img-lazy src="https://lorempixel.com/600/400/cats/5/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
<b-img-lazy src="https://lorempixel.com/600/400/cats/6/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
<b-img-lazy src="https://lorempixel.com/600/400/cats/7/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
<b-img-lazy src="https://lorempixel.com/600/400/cats/8/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
<b-img-lazy src="https://lorempixel.com/600/400/cats/9/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
<b-img-lazy src="https://lorempixel.com/600/400/cats/10/" center fluid-grow width="600" height="400" blank-color="#bbb" alt="img" class="my-5" />
</div>
<!-- b-img-lazy.vue -->
```
@@ -1,5 +1,6 @@
{
"title": "Images",
"new": true,
"component": "bImg"
"component": "bImg",
"components": [ "bImgLazy" ],
"new": true
}
@@ -0,0 +1,169 @@
<template>
<b-img :src="computedSrc"
:blank="computedBlank"
:blank-color="blankColor"
:width="computedWidth"
:height="computedHeight"
:fluid="fluid"
:fluid-grow="fluidGrow"
:block="block"
:thumbnail="thumbnail"
:rounded="rounded"
:left="left"
:right="right"
:center="center"
></b-img>
</template>
<script>
import bImg from './img';
const THROTTLE = 100;
export default {
components: { bImg },
data() {
return {
isShown: false,
scrollTimeout: null
}
},
props: {
src: {
type: String,
default: null,
rqeuired: true
},
width: {
type: [Number, String],
default: null
},
height: {
type: [Number, String],
default: null
},
blankSrc: {
// If null, a blank image is generated
type: String,
default: null
},
blankColor: {
type: String,
default: 'transparent'
},
blankWidth: {
type: [Number, String],
default: null
},
blankHeight: {
type: [Number, String],
default: null
},
fluid: {
type: Boolean,
default: false
},
fluidGrow: {
type: Boolean,
default: false
},
block: {
type: Boolean,
default: false
},
thumbnail: {
type: Boolean,
default: false
},
rounded: {
type: [Boolean, String],
default: false
},
left: {
type: Boolean,
default: false
},
right: {
type: Boolean,
default: false
},
center: {
type: Boolean,
default: false
},
offset: {
type: [Number, String],
default: 360
},
throttle: {
type: [Number, String],
default: THROTTLE
}
},
computed: {
computedSrc() {
return (!this.blankSrc || this.isShown) ? this.src : this.blankSrc;
},
computedBlank() {
return (this.isShown || this.blankSrc) ? false : true;
},
computedWidth() {
return this.isShown ? this.width : (this.blankWidth || this.width);
},
computedHeight() {
return this.isShown ? this.height : (this.blankHeight || this.height);
}
},
mounted() {
this.setListeners(true);
this.checkView();
},
beforeDdestroy() {
this.setListeners(flase);
},
methods: {
setListeners(on) {
const root = window;
if (on) {
root.addEventListener('scroll', this.onScroll);
root.addEventListener('resize', this.onScroll);
root.addEventListener('orientationchange', this.onScroll);
} else {
root.removeEventListener('scroll', this.onScroll);
root.removeEventListener('resize', this.onScroll);
root.removeEventListener('orientationchange', this.onScroll);
clearTimeout(this.scrollTimer);
this.scrollTimout = null;
}
},
checkView() {
// check bounding box + offset to see if we should show
if (this.$el.offsetParent === null || !(this.$el.offsetWidth > 0 || this.$el.offsetHeight > 0)) {
// Element is hidden, so skip for now
return;
}
const offset = parseInt(this.offset,10) || 0;
const view = {
left: 0 - offset,
top: 0 - offset,
bottom: document.documentElement.clientHeight + offset,
right: document.documentElement.clientWidth + offset
};
const box = this.$el.getBoundingClientRect();
if (box.right >= view.left && box.bottom >= view.top && box.left <= view.right && box.top <= view.bottom) {
// image is in view (or about to be in view)
this.isShown = true;
this.setListeners(false);
}
},
onScroll() {
if (this.isShown) {
this.setListeners(false);
return;
}
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(this.checkView, parseInt(this.throttle, 10) || THROTTLE);
}
}
};
</script>
@@ -37,6 +37,7 @@ import bFormTextarea from "./form-textarea.vue";
import bFormFile from "./form-file.vue";
import bFormSelect from "./form-select.vue";
import bImg from "./img";
import bImgLazy from "./img-lazy.vue";
import bJumbotron from "./jumbotron";
import bLink from "./link";
import bListGroup from "./list-group";
@@ -112,6 +113,7 @@ export {
bFormTextarea,
bFormSelect,
bImg,
bImgLazy,
bJumbotron,
bBadge,
bMedia,

0 comments on commit 68138cb

Please sign in to comment.