Skip to content

Commit a671be1

Browse files
committed
Merge branch 'image' into woothu
2 parents 7ffcf0d + 41da477 commit a671be1

File tree

4 files changed

+264
-0
lines changed

4 files changed

+264
-0
lines changed

src/components/Image/CImage.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { mergeData } from 'vue-functional-data-merge'
2+
3+
// Blank image with fill template
4+
const BLANK_TEMPLATE = '<svg width="%{w}" height="%{h}" ' +
5+
'xmlns="http://www.w3.org/2000/svg" ' +
6+
'viewBox="0 0 %{w} %{h}" preserveAspectRatio="none">' +
7+
'<rect width="100%" height="100%" style="fill:%{f};"></rect>' +
8+
'</svg>'
9+
10+
function makeBlankImgSrc (width, height, color) {
11+
const src = encodeURIComponent(
12+
BLANK_TEMPLATE
13+
.replace('%{w}', String(width))
14+
.replace('%{h}', String(height))
15+
.replace('%{f}', color)
16+
)
17+
return `data:image/svg+xml;charset=UTF-8,${src}`
18+
}
19+
20+
export const props = {
21+
src: String,
22+
alt: String,
23+
width: [Number, String],
24+
height: [Number, String],
25+
block: Boolean,
26+
fluid: Boolean,
27+
// Gives fluid images class `w-100` to make them grow to fit container
28+
fluidGrow: Boolean,
29+
rounded: {
30+
// rounded can be:
31+
// false: no rounding of corners
32+
// true: slightly rounded corners
33+
// 'top': top corners rounded
34+
// 'right': right corners rounded
35+
// 'bottom': bottom corners rounded
36+
// 'left': left corners rounded
37+
// 'circle': circle/oval
38+
// '0': force rounding off
39+
type: [Boolean, String],
40+
default: false
41+
},
42+
thumbnail: Boolean,
43+
left: Boolean,
44+
right: Boolean,
45+
center: Boolean,
46+
blank: Boolean,
47+
blankColor: {
48+
type: String,
49+
default: 'transparent'
50+
}
51+
}
52+
53+
export default {
54+
functional: true,
55+
name: 'CImage',
56+
props,
57+
render (h, { props, data }) {
58+
let src = props.src
59+
let width = parseInt(props.width, 10) ? parseInt(props.width, 10) : null
60+
let height = parseInt(props.height, 10) ? parseInt(props.height, 10) : null
61+
let align = null
62+
let block = props.block
63+
if (props.blank) {
64+
if (!height && Boolean(width)) {
65+
height = width
66+
} else if (!width && Boolean(height)) {
67+
width = height
68+
}
69+
if (!width && !height) {
70+
width = 1
71+
height = 1
72+
}
73+
// Make a blank SVG image
74+
src = makeBlankImgSrc(width, height, props.blankColor || 'transparent')
75+
}
76+
if (props.left) {
77+
align = 'float-left'
78+
} else if (props.right) {
79+
align = 'float-right'
80+
} else if (props.center) {
81+
align = 'mx-auto'
82+
block = true
83+
}
84+
return h(
85+
'img',
86+
mergeData(data, {
87+
attrs: {
88+
'src': src,
89+
'alt': props.alt,
90+
'width': width ? String(width) : null,
91+
'height': height ? String(height) : null
92+
},
93+
class: {
94+
'img-thumbnail': props.thumbnail,
95+
'img-fluid': props.fluid || props.fluidGrow,
96+
'w-100': props.fluidGrow,
97+
'rounded': props.rounded === '' || props.rounded === true,
98+
[`rounded-${props.rounded}`]: typeof props.rounded === 'string' && props.rounded !== '',
99+
[align]: Boolean(align),
100+
'd-block': block
101+
}
102+
})
103+
)
104+
}
105+
}

src/components/Image/CImageLazy.js

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import CImage from './CImage'
2+
import { isVisible, getBCR, eventOn, eventOff } from '../../utils/dom'
3+
const THROTTLE = 100
4+
5+
// @vue/component
6+
export default {
7+
components: { CImage },
8+
name: 'CImageLazy',
9+
props: {
10+
src: {
11+
type: String,
12+
required: true
13+
},
14+
alt: String,
15+
width: [Number, String],
16+
height: [Number, String],
17+
// If null, a blank image is generated
18+
blankSrc: String,
19+
blankColor: {
20+
type: String,
21+
default: 'transparent'
22+
},
23+
blankWidth: [Number, String],
24+
blankHeight: [Number, String],
25+
fluid: Boolean,
26+
fluidGrow: Boolean,
27+
block: Boolean,
28+
thumbnail: Boolean,
29+
rounded: {
30+
type: [Boolean, String],
31+
default: false
32+
},
33+
left: Boolean,
34+
right: Boolean,
35+
center: Boolean,
36+
offset: {
37+
type: [Number, String],
38+
default: 360
39+
},
40+
throttle: {
41+
type: [Number, String],
42+
default: THROTTLE
43+
}
44+
},
45+
data () {
46+
return {
47+
isShown: false,
48+
scrollTimeout: null
49+
}
50+
},
51+
computed: {
52+
computedSrc () {
53+
return (!this.blankSrc || this.isShown) ? this.src : this.blankSrc
54+
},
55+
computedBlank () {
56+
return !((this.isShown || this.blankSrc))
57+
},
58+
computedWidth () {
59+
return this.isShown ? this.width : (this.blankWidth || this.width)
60+
},
61+
computedHeight () {
62+
return this.isShown ? this.height : (this.blankHeight || this.height)
63+
}
64+
},
65+
mounted () {
66+
this.setListeners(true)
67+
this.checkView()
68+
},
69+
activated () {
70+
this.setListeners(true)
71+
this.checkView()
72+
},
73+
deactivated () {
74+
this.setListeners(false)
75+
},
76+
methods: {
77+
setListeners (on) {
78+
clearTimeout(this.scrollTimer)
79+
this.scrollTimeout = null
80+
const root = window
81+
if (on) {
82+
eventOn(root, 'scroll', this.onScroll)
83+
eventOn(root, 'resize', this.onScroll)
84+
eventOn(root, 'orientationchange', this.onScroll)
85+
} else {
86+
eventOff(root, 'scroll', this.onScroll)
87+
eventOff(root, 'resize', this.onScroll)
88+
eventOff(root, 'orientationchange', this.onScroll)
89+
}
90+
},
91+
checkView () {
92+
// check bounding box + offset to see if we should show
93+
if (!isVisible(this.$el)) {
94+
// Element is hidden, so skip for now
95+
return
96+
}
97+
const offset = parseInt(this.offset, 10) || 0
98+
const docElement = document.documentElement
99+
const view = {
100+
l: 0 - offset,
101+
t: 0 - offset,
102+
b: docElement.clientHeight + offset,
103+
r: docElement.clientWidth + offset
104+
}
105+
const box = getBCR(this.$el)
106+
if (box.right >= view.l && box.bottom >= view.t && box.left <= view.r && box.top <= view.b) {
107+
// image is in view (or about to be in view)
108+
this.isShown = true
109+
this.setListeners(false)
110+
}
111+
},
112+
onScroll () {
113+
if (this.isShown) {
114+
this.setListeners(false)
115+
} else {
116+
clearTimeout(this.scrollTimeout)
117+
this.scrollTimeout = setTimeout(this.checkView, parseInt(this.throttle, 10) || THROTTLE)
118+
}
119+
}
120+
},
121+
render (h) {
122+
return h(
123+
'CImage',
124+
{
125+
props: {
126+
src: this.computedSrc,
127+
alt: this.alt,
128+
blank: this.computedBlank,
129+
blankColor: this.blankColor,
130+
width: this.computedWidth,
131+
height: this.computedHeight,
132+
fluid: this.fluid,
133+
fluidGrow: this.fluidGrow,
134+
block: this.block,
135+
thumbnail: this.thumbnail,
136+
rounded: this.rounded,
137+
left: this.left,
138+
right: this.right,
139+
center: this.center
140+
}
141+
}
142+
)
143+
},
144+
beforeDdestroy () {
145+
this.setListeners(false)
146+
}
147+
}

src/components/Image/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import CImage from './CImage'
2+
import CImageLazy from './CImageLazy'
3+
4+
5+
export {
6+
CImage,
7+
CImageLazy
8+
}

src/components/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export * from './Alert'
1515
export * from './Button'
1616
export * from './Card'
1717
export * from './Dropdown'
18+
<<<<<<< HEAD
1819
export * from './ListGroup'
1920
<<<<<<< HEAD
2021
<<<<<<< HEAD
@@ -33,3 +34,6 @@ export * from './Media'
3334
=======
3435
export * from './Jumbotron'
3536
>>>>>>> jumbotron
37+
=======
38+
export * from './Image'
39+
>>>>>>> image

0 commit comments

Comments
 (0)