Skip to content

Commit 4c8c074

Browse files
committed
basic support for background images
1 parent b146e57 commit 4c8c074

File tree

4 files changed

+152
-43
lines changed

4 files changed

+152
-43
lines changed

src/css-utils.js

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import CSS_COLOR_NAMES from "./html-colors";
22
import toPx from "./css-length-converter";
3+
import {element} from 'prop-types'
34

45
function _getAttributeFromString(string, method, ...data) {
56
if (!string) return false
67
string = string.split(' ')
78
for (let i in string) {
9+
if (!string.hasOwnProperty(i)) continue
810
const res = method(string, Number(i), ...data)
911
if (res) return res
1012
}
@@ -26,13 +28,43 @@ function _getColor(border, i) {
2628
return color
2729
}
2830
// color is a html color name
29-
if (
30-
CSS_COLOR_NAMES.map(color => color.toLowerCase())
31-
.includes(val.toLowerCase())
32-
) return val
31+
if (CSS_COLOR_NAMES.map(color => color.toLowerCase())
32+
.includes(val.toLowerCase()))
33+
return val
34+
if (val === 'currentcolor') {
35+
return 'currentcolor'
36+
}
3337
return false
3438
}
3539

40+
function _getImage(border, i) {
41+
const val = border[i]
42+
43+
if (val.startsWith('url')) {
44+
let url = val;
45+
for (let j = 1; border[i + j]; j++) {
46+
url += border[i + j]
47+
}
48+
url = /url\(("[^"]+"|'[^']+'|[^)]+)\)/g.exec(url)
49+
url = url ? url[1] : false
50+
url = url?.startsWith('"') || url?.startsWith("'")
51+
? url.substr(1, url.length - 2)
52+
: url
53+
return url
54+
}
55+
}
56+
57+
function _getImageSize(border, i, element) {
58+
const val = border[i]
59+
60+
if (['cover', 'contain'].includes(val) || val.endsWith('%')) {
61+
return val
62+
}
63+
unitCheck(val, htmlBackgroundSizeNotSvgError)
64+
if (val.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/))
65+
return toPx(element, val)
66+
}
67+
3668
function _getOpacity(border, i) {
3769
let val = border[i]
3870
if (val.startsWith('rgba') || val.startsWith('hsla')) {
@@ -46,10 +78,14 @@ function _getOpacity(border, i) {
4678
return 1
4779
}
4880

49-
const htmlLengthNotSvgError = new Error('<RoundDiv> Border lengths must be either "thin", "medium", "thick", or in one of the following units: ch, cm, em, ex, in, mm, pc, pt, px, rem, vh, vmax, vmin, vw.')
81+
const htmlLengthNotSvgErrorTemplate = (a, b) => `<RoundDiv> ${a} must be ${b ? `either ${b}, or` : ''} in one of the following units: ch, cm, em, ex, in, mm, pc, pt, px, rem, vh, vmax, vmin, vw.`
82+
const htmlBorderLengthNotSvgError =
83+
new Error(htmlLengthNotSvgErrorTemplate('border lengths', '"thin", "medium", "thick"'))
84+
const htmlBackgroundSizeNotSvgError =
85+
new Error(htmlLengthNotSvgErrorTemplate('background size', '"cover", "contain"'))
5086

51-
function unitCheck(length) {
52-
if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) throw htmlLengthNotSvgError
87+
function unitCheck(length, err) {
88+
if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) throw err
5389
return length
5490
}
5591

@@ -61,15 +97,17 @@ function _getWidth(border, i, element) {
6197
if (val.toLowerCase() === 'thin') return 1
6298
if (val.toLowerCase() === 'medium') return 3
6399
if (val.toLowerCase() === 'thick') return 5
64-
unitCheck(val)
100+
unitCheck(val, htmlBorderLengthNotSvgError)
65101
// width is <length>
66102
if (val.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/))
67103
return toPx(element, val)
68104
return false
69105
}
70106

71-
const getWidth = s => _getAttributeFromString(s, _getWidth),
107+
const getWidth = (s, el) => _getAttributeFromString(s, _getWidth, el),
108+
getImage = s => _getAttributeFromString(s, _getImage),
109+
getImageSize = (s, el) => _getAttributeFromString(s, _getImageSize, el),
72110
getColor = s => _getAttributeFromString(s, _getColor),
73111
getOpacity = s => _getAttributeFromString(s, _getOpacity)
74112

75-
export {getWidth, getColor, unitCheck, getOpacity}
113+
export {getWidth, getImage, getImageSize, getColor, getOpacity}

src/main.js

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export default function RoundDiv({clip, style, children, ...props}) {
1010
const [width, setWidth] = useState(0)
1111
const [radius, setRadius] = useState(0)
1212
const [background, setBackground] = useState('transparent')
13+
const [backgroundImage, setBackgroundImage] = useState('none')
14+
const [backgroundImageSize, setBackgroundImageSize] = useState(null)
1315
const [backgroundOpacity, setBackgroundOpacity] = useState(0)
1416
const [borderColor, setBorderColor] = useState('transparent')
1517
const [borderWidth, setBorderWidth] = useState(0)
@@ -30,6 +32,8 @@ export default function RoundDiv({clip, style, children, ...props}) {
3032
setWidth,
3133
setRadius,
3234
setBackground,
35+
setBackgroundImage,
36+
setBackgroundImageSize,
3337
setBackgroundOpacity,
3438
setBorderColor,
3539
setBorderWidth,
@@ -47,12 +51,46 @@ export default function RoundDiv({clip, style, children, ...props}) {
4751
}
4852

4953
divStyle.background = 'transparent'
50-
divStyle.borderWidth = divStyle.borderWidth || '0'
54+
divStyle.borderWidth = '0'
5155
divStyle.borderColor = 'transparent'
5256

57+
const [backgroundImageAspectRatio, setBackgroundImageAspectRatio] = useState(1)
58+
const [backgroundImageHeight, setBackgroundImageHeight] = useState(0)
59+
const [backgroundImageWidth, setBackgroundImageWidth] = useState(0)
60+
useEffect(() => {
61+
const img = new Image()
62+
img.onload = () => {
63+
setBackgroundImageAspectRatio(img.naturalWidth / img.naturalHeight)
64+
setBackgroundImageHeight(img.naturalHeight)
65+
setBackgroundImageWidth(img.naturalWidth)
66+
}
67+
img.src = backgroundImage
68+
}, [backgroundImage, setBackgroundImageAspectRatio])
69+
70+
const fullHeight = height + borderWidth * 2,
71+
fullWidth = width + borderWidth * 2
72+
73+
console.log(backgroundImageSize)
74+
75+
const lengthCalculator = (isWidth) => {
76+
if ((!isWidth && backgroundImageAspectRatio > 1)
77+
|| (isWidth && backgroundImageAspectRatio < 1)
78+
|| !backgroundImageSize
79+
) return undefined
80+
81+
if (typeof backgroundImageSize === 'number')
82+
return backgroundImageSize
83+
84+
if (['cover', 'contain'].includes(backgroundImageSize))
85+
return fullHeight
86+
87+
if (backgroundImageSize.endsWith('%'))
88+
return (isWidth ? fullWidth : fullHeight)
89+
* (Number(backgroundImageSize.replace('%', '')) / 100)
90+
}
91+
5392
return <div {...props} style={divStyle} ref={div}>
5493
<ShadowRoot>
55-
<style>{':host{position:relative}'}</style>
5694
<svg viewBox={`0 0 ${width} ${height}`} style={{
5795
position: 'absolute',
5896
height,
@@ -62,15 +100,23 @@ export default function RoundDiv({clip, style, children, ...props}) {
62100
}} xmlnsXlink="http://www.w3.org/1999/xlink" preserveAspectRatio={'xMidYMid slice'}>
63101
<defs>
64102
<path d={
65-
generateSvgSquircle(height + borderWidth * 2, width + borderWidth * 2, radius, clip)
103+
generateSvgSquircle(fullHeight, fullWidth, radius, clip)
66104
} id="shape"/>
67105

106+
<pattern id="bg" patternUnits="userSpaceOnUse"
107+
width={fullWidth} height={fullHeight}>
108+
<image href={backgroundImage} x={borderWidth} y={borderWidth}
109+
preserveAspectRatio={'xMinYMin ' + (backgroundImageSize === 'contain' ? 'meet' : 'slice')}
110+
height={lengthCalculator(false)}
111+
width={lengthCalculator(true)}/>
112+
</pattern>
113+
68114
<clipPath id="insideOnly">
69115
<use xlinkHref="#shape" fill="black"/>
70116
</clipPath>
71117
</defs>
72-
<use xlinkHref="#shape" fill={background} opacity={backgroundOpacity}
73-
x={-borderWidth} y={-borderWidth}/>
118+
<use xlinkHref="#shape" fill={backgroundImage !== 'none' ? 'url(#bg)' : background}
119+
opacity={backgroundOpacity} x={-borderWidth} y={-borderWidth}/>
74120
<use xlinkHref="#shape" stroke={borderColor} fill="none" strokeWidth={borderWidth * 2}
75121
opacity={borderOpacity} clipPath="url(#insideOnly)" x={-borderWidth} y={-borderWidth}/>
76122
</svg>

src/style-sheet-watcher.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
let watchers = 0
21
const CSSChangeEvent = new CustomEvent('css-change');
32

43
export default function attachCSSWatcher(callback) {
5-
watchers++
6-
console.log(watchers)
74
CSSWatcher.addEventListener('css-change', () => callback())
85
}
96

src/updateStates.js

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {getColor, getOpacity, getWidth, unitCheck} from "./css-utils";
1+
import {getColor, getImage, getImageSize, getOpacity, getWidth} from "./css-utils";
22
import getStyle from "./styles-extractor";
33

44
export default function updateStates(args) {
@@ -9,6 +9,8 @@ export default function updateStates(args) {
99
setWidth,
1010
setRadius,
1111
setBackground,
12+
setBackgroundImage,
13+
setBackgroundImageSize,
1214
setBackgroundOpacity,
1315
setBorderColor,
1416
setBorderWidth,
@@ -19,40 +21,66 @@ export default function updateStates(args) {
1921
setHeight(boundingClientRect.height)
2022
setWidth(boundingClientRect.width)
2123
}
22-
const divStyle = boundingClientRect ? window?.getComputedStyle(div.current) : null
24+
25+
function camelise(str) {
26+
return str?.replace(/^\w|[A-Z]|\b\w|\s+/g, function (match, index) {
27+
if (+match === 0) return "";
28+
return index === 0 ? match.toLowerCase() : match.toUpperCase();
29+
}).replace(/-/g, '');
30+
}
31+
32+
const getNthStyle = (key, n) =>
33+
(getStyle(camelise(key), div.current)?.overwritten || [])[n]?.value,
34+
35+
getNthStyleAttrOrAlt = (...args) => {
36+
args = Array.from(args)
37+
let n = args.pop()
38+
if (typeof n !== 'number') {
39+
args.push(n)
40+
n = 0
41+
}
42+
43+
let a, b, c, d
44+
if (args.length === 2)
45+
[a, b, c, d] = [args[0], args[1], args[0], args[1]]
46+
else if (args.length === 3)
47+
[a, b, c, d] = [args[0], args[1], args[1], args[2]]
48+
49+
return style ? style[camelise(a)] || style[camelise(b)]
50+
: getNthStyle(c, n) || getNthStyle(d, n)
51+
},
52+
getNthStyleAttr = (a, n) =>
53+
style ? style[camelise(a)] : getNthStyle(a, n)
54+
55+
const divStyle = div.current ? window?.getComputedStyle(div.current) : null
2356
if (divStyle) {
2457
setRadius(Number(
2558
(divStyle.borderRadius || divStyle.borderTopLeftRadius)
26-
.replace('px', '')))
59+
.replace('px', ''))
60+
)
2761
setBackground(getColor(
28-
style?.background
29-
|| style?.backgroundColor
30-
|| (getStyle('background', div.current)?.overwritten || [])[1]?.value
31-
|| (getStyle('background-color', div.current)?.overwritten || [])[1]?.value
62+
getNthStyleAttrOrAlt('background', 'background-color', 1)
3263
) || 'transparent')
64+
setBackgroundImage(getImage(
65+
getNthStyleAttrOrAlt('background', 'background-image', 1)
66+
) || 'none')
67+
setBackgroundImageSize(getImageSize(
68+
getNthStyleAttr('background-size', 1)
69+
) || null)
3370
setBackgroundOpacity(getOpacity(
34-
style?.background
35-
|| style?.backgroundColor
36-
|| (getStyle('background', div.current)?.overwritten || [])[1]?.value
37-
|| (getStyle('background-color', div.current)?.overwritten || [])[1]?.value
71+
getNthStyleAttrOrAlt('background', 'background-color', 1)
3872
) || 1)
73+
3974
setBorderColor(getColor(
40-
style?.border
41-
|| style?.borderColor
42-
|| (getStyle('borderColor', div.current)?.overwritten || [])[1]?.value
43-
|| (getStyle('borderTopColor', div.current)?.overwritten || [])[1]?.value
75+
getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1)
4476
) || 'transparent')
45-
setBorderWidth(getWidth(
46-
style?.border
47-
|| style?.borderWidth
48-
|| unitCheck((getStyle('borderWidth', div.current)?.overwritten || [])[0]?.value)
49-
|| unitCheck((getStyle('borderTopWidth', div.current)?.overwritten || [])[0]?.value),
50-
div.current) || 0)
5177
setBorderOpacity(getOpacity(
52-
style?.border
53-
|| style?.borderColor
54-
|| (getStyle('borderColor', div.current)?.overwritten || [])[1]?.value
55-
|| (getStyle('borderTopColor', div.current)?.overwritten || [])[1]?.value
78+
getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1)
5679
) || 1)
80+
81+
setBorderWidth(getWidth(
82+
getNthStyleAttrOrAlt('border', 'border-width', 'border-top-width', 0),
83+
div.current
84+
) || 0)
5785
}
5886
}

0 commit comments

Comments
 (0)