Skip to content

Commit 5a63e4e

Browse files
committed
complete background image size support, basic repeat support
1 parent 4c8c074 commit 5a63e4e

File tree

4 files changed

+125
-73
lines changed

4 files changed

+125
-73
lines changed

.babelrc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
{
22
"presets": [
33
"@babel/react",
4-
"@babel/env",
5-
"minify"
4+
"@babel/env"
65
],
76
"plugins": [
87
"@babel/plugin-proposal-class-properties"

src/css-utils.js

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

54
function _getAttributeFromString(string, method, ...data) {
65
if (!string) return false
@@ -12,16 +11,16 @@ function _getAttributeFromString(string, method, ...data) {
1211
}
1312
}
1413

15-
function _getColor(border, i) {
16-
const val = border[i]
14+
function _getColor(b, i) {
15+
const val = b[i]
1716
// color is a hex code
1817
if (val.toLowerCase().match(/#([0-9a-f]{3}){1,2}/)) return val
1918
// color is a function (rgb, rgba, hsl, hsla)
2019
if (val.startsWith('rgb') || val.startsWith('hsl')) {
2120
let color = val;
2221
if (!val.endsWith(')'))
23-
for (let j = 1; !border[i + j - 1].endsWith(')'); j++) {
24-
color += border[i + j]
22+
for (let j = 1; !b[i + j - 1].endsWith(')'); j++) {
23+
color += b[i + j]
2524
}
2625
if (color[3] === 'a')
2726
color = color.replace('a', '').replace(/,[^),]+\)/, ')')
@@ -37,13 +36,13 @@ function _getColor(border, i) {
3736
return false
3837
}
3938

40-
function _getImage(border, i) {
41-
const val = border[i]
39+
function _getImage(b, i) {
40+
const val = b[i]
4241

4342
if (val.startsWith('url')) {
4443
let url = val;
45-
for (let j = 1; border[i + j]; j++) {
46-
url += border[i + j]
44+
for (let j = 1; b[i + j]; j++) {
45+
url += b[i + j]
4746
}
4847
url = /url\(("[^"]+"|'[^']+'|[^)]+)\)/g.exec(url)
4948
url = url ? url[1] : false
@@ -54,38 +53,66 @@ function _getImage(border, i) {
5453
}
5554
}
5655

57-
function _getImageSize(border, i, element) {
58-
const val = border[i]
56+
function _getImageSize(b, i, element) {
57+
// "val" is always what is defined in backgrund-size ([i]th argument)
58+
const val = b[i]
5959

60-
if (['cover', 'contain'].includes(val) || val.endsWith('%')) {
61-
return val
60+
if (['cover', 'contain'].includes(val)) {
61+
return [val, null]
6262
}
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)
63+
64+
const returnIfCSSNumeric = (val, throwErr) => {
65+
if (val?.endsWith('%'))
66+
return val
67+
else if (val?.match(/(\d+(\.\d+)?(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)|0)/)) {
68+
unitCheck(val, throwErr ? htmlBackgroundSizeNotSvgError : undefined)
69+
return toPx(element, val)
70+
} else
71+
return null
72+
}
73+
74+
const convertedVal = returnIfCSSNumeric(val, true) // has null as fallback already
75+
// "background-size: 50% 50%" is different to "background-size: 50%"
76+
return [convertedVal, returnIfCSSNumeric(b[i + 1])]
6677
}
6778

68-
function _getOpacity(border, i) {
69-
let val = border[i]
79+
function _getOpacity(b, i) {
80+
let val = b[i]
7081
if (val.startsWith('rgba') || val.startsWith('hsla')) {
7182
if (!val.endsWith(')'))
72-
for (let j = 1; !border[i + j - 1].endsWith(')'); j++) {
73-
val += border[i + j]
83+
for (let j = 1; !b[i + j - 1].endsWith(')'); j++) {
84+
val += b[i + j]
7485
}
7586
return val.replace(/(rgb|hsl)a?\(([^,)]+,){3}/, '').replace(/\)$/, '')
7687
}
77-
if (border.length - 1 === i)
88+
if (b.length - 1 === i)
7889
return 1
7990
}
8091

92+
function _getRepeat(b, i) {
93+
let val = b[i]
94+
if (val === 'repeat-x')
95+
return ['repeat', 'no-repeat']
96+
else if (val === 'repeat-y')
97+
return ['no-repeat', 'repeat']
98+
else if (['repeat', 'space', 'round', 'no-repeat'].includes(val)) {
99+
if (['repeat', 'space', 'round', 'no-repeat'].includes(b[i + 1] || ''))
100+
return [val, b[i + 1]]
101+
else
102+
return [val, val]
103+
}
104+
}
105+
81106
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.`
82107
const htmlBorderLengthNotSvgError =
83108
new Error(htmlLengthNotSvgErrorTemplate('border lengths', '"thin", "medium", "thick"'))
84109
const htmlBackgroundSizeNotSvgError =
85110
new Error(htmlLengthNotSvgErrorTemplate('background size', '"cover", "contain"'))
86111

87112
function unitCheck(length, err) {
88-
if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g)) throw err
113+
if (length?.match(/(cap|ic|lh|rlh|vi|vm|vb|Q|mozmm)/g))
114+
if (err) throw err
115+
else return false
89116
return length
90117
}
91118

@@ -104,10 +131,17 @@ function _getWidth(border, i, element) {
104131
return false
105132
}
106133

134+
/** @returns {number} */
107135
const getWidth = (s, el) => _getAttributeFromString(s, _getWidth, el),
136+
/** @returns {string} */
108137
getImage = s => _getAttributeFromString(s, _getImage),
138+
/** @returns {Array<string|number>} */
109139
getImageSize = (s, el) => _getAttributeFromString(s, _getImageSize, el),
140+
/** @returns {string} */
110141
getColor = s => _getAttributeFromString(s, _getColor),
142+
/** @returns {Array<string>} */
143+
getRepeat = s => _getAttributeFromString(s, _getRepeat),
144+
/** @returns {number} */
111145
getOpacity = s => _getAttributeFromString(s, _getOpacity)
112146

113-
export {getWidth, getImage, getImageSize, getColor, getOpacity}
147+
export {getWidth, getImage, getImageSize, getColor, getRepeat, getOpacity}

src/main.js

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@ import ShadowRoot from "./react-shadow-dom"
66
import attachCSSWatcher from './style-sheet-watcher'
77

88
export default function RoundDiv({clip, style, children, ...props}) {
9+
// welcome to react states hell
910
const [height, setHeight] = useState(0)
1011
const [width, setWidth] = useState(0)
1112
const [radius, setRadius] = useState(0)
13+
1214
const [background, setBackground] = useState('transparent')
1315
const [backgroundImage, setBackgroundImage] = useState('none')
14-
const [backgroundImageSize, setBackgroundImageSize] = useState(null)
16+
// todo: background size two values (from css)
17+
const [backgroundImageSize, setBackgroundImageSize] = useState([null, null])
1518
const [backgroundOpacity, setBackgroundOpacity] = useState(0)
19+
const [backgroundRepeat, setBackgroundRepeat] = useState(['repeat', 'repeat'])
20+
1621
const [borderColor, setBorderColor] = useState('transparent')
1722
const [borderWidth, setBorderWidth] = useState(0)
1823
const [borderOpacity, setBorderOpacity] = useState(1)
@@ -35,6 +40,7 @@ export default function RoundDiv({clip, style, children, ...props}) {
3540
setBackgroundImage,
3641
setBackgroundImageSize,
3742
setBackgroundOpacity,
43+
setBackgroundRepeat,
3844
setBorderColor,
3945
setBorderWidth,
4046
setBorderOpacity
@@ -55,40 +61,56 @@ export default function RoundDiv({clip, style, children, ...props}) {
5561
divStyle.borderColor = 'transparent'
5662

5763
const [backgroundImageAspectRatio, setBackgroundImageAspectRatio] = useState(1)
58-
const [backgroundImageHeight, setBackgroundImageHeight] = useState(0)
59-
const [backgroundImageWidth, setBackgroundImageWidth] = useState(0)
64+
// const [backgroundImageHeight, setBackgroundImageHeight] = useState(0)
65+
// const [backgroundImageWidth, setBackgroundImageWidth] = useState(0)
6066
useEffect(() => {
6167
const img = new Image()
6268
img.onload = () => {
6369
setBackgroundImageAspectRatio(img.naturalWidth / img.naturalHeight)
64-
setBackgroundImageHeight(img.naturalHeight)
65-
setBackgroundImageWidth(img.naturalWidth)
70+
// setBackgroundImageHeight(img.naturalHeight)
71+
// setBackgroundImageWidth(img.naturalWidth)
6672
}
6773
img.src = backgroundImage
6874
}, [backgroundImage, setBackgroundImageAspectRatio])
6975

7076
const fullHeight = height + borderWidth * 2,
7177
fullWidth = width + borderWidth * 2
7278

73-
console.log(backgroundImageSize)
74-
7579
const lengthCalculator = (isWidth) => {
76-
if ((!isWidth && backgroundImageAspectRatio > 1)
77-
|| (isWidth && backgroundImageAspectRatio < 1)
78-
|| !backgroundImageSize
79-
) return undefined
80+
let n = isWidth ? 0 : 1
81+
82+
if (backgroundImageSize[0] === 'contain')
83+
if (backgroundImageAspectRatio > 1)
84+
return isWidth ? width : (height / backgroundImageAspectRatio)
85+
else
86+
return isWidth ? (width * backgroundImageAspectRatio) : height
8087

81-
if (typeof backgroundImageSize === 'number')
82-
return backgroundImageSize
88+
if (['cover', 'contain'].includes(backgroundImageSize[0]))
89+
return isWidth ? width : height
8390

84-
if (['cover', 'contain'].includes(backgroundImageSize))
85-
return fullHeight
91+
if (backgroundImageSize[n] === null && !!backgroundImageSize[0])
92+
return lengthCalculator(true) *
93+
(backgroundImageAspectRatio < 1
94+
? 1 / backgroundImageAspectRatio
95+
: backgroundImageAspectRatio
96+
)
8697

87-
if (backgroundImageSize.endsWith('%'))
88-
return (isWidth ? fullWidth : fullHeight)
89-
* (Number(backgroundImageSize.replace('%', '')) / 100)
98+
if (!backgroundImageSize[n])
99+
return undefined
100+
101+
if (backgroundImageSize[n]?.endsWith('%'))
102+
return width * (Number(backgroundImageSize[n].replace('%', '')) / 100)
103+
104+
if (typeof backgroundImageSize[n] === 'number')
105+
return backgroundImageSize[n]
90106
}
91107

108+
const imageHeight = lengthCalculator(false),
109+
imageWidth = lengthCalculator(true),
110+
preserveImageAspectRatio = (
111+
['cover', 'contain'].includes(backgroundImageSize[0])
112+
)
113+
92114
return <div {...props} style={divStyle} ref={div}>
93115
<ShadowRoot>
94116
<svg viewBox={`0 0 ${width} ${height}`} style={{
@@ -102,13 +124,19 @@ export default function RoundDiv({clip, style, children, ...props}) {
102124
<path d={
103125
generateSvgSquircle(fullHeight, fullWidth, radius, clip)
104126
} id="shape"/>
105-
127+
{/* todo: support for "repeat: space" and "repeat: round" */}
106128
<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)}/>
129+
width={backgroundRepeat[0] === 'no-repeat' ? fullWidth : imageWidth}
130+
height={backgroundRepeat[1] === 'no-repeat' ? fullHeight : imageHeight}
131+
x={borderWidth} y={borderWidth}>
132+
<image href={backgroundImage}
133+
preserveAspectRatio={preserveImageAspectRatio ? 'xMinYMin ' + (
134+
backgroundImageSize[0] === 'contain' || backgroundImageSize[0]?.endsWith('%')
135+
? 'meet'
136+
: 'slice'
137+
) : 'none'}
138+
height={imageHeight}
139+
width={imageWidth}/>
112140
</pattern>
113141

114142
<clipPath id="insideOnly">

src/updateStates.js

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

44
export default function updateStates(args) {
5-
const {
6-
div,
7-
style,
8-
setHeight,
9-
setWidth,
10-
setRadius,
11-
setBackground,
12-
setBackgroundImage,
13-
setBackgroundImageSize,
14-
setBackgroundOpacity,
15-
setBorderColor,
16-
setBorderWidth,
17-
setBorderOpacity
18-
} = args
5+
const {div, style, setHeight, setWidth} = args
196
const boundingClientRect = div.current?.getBoundingClientRect()
207
if (boundingClientRect) {
218
setHeight(boundingClientRect.height)
@@ -54,31 +41,35 @@ export default function updateStates(args) {
5441

5542
const divStyle = div.current ? window?.getComputedStyle(div.current) : null
5643
if (divStyle) {
57-
setRadius(Number(
44+
let states = args
45+
states.setRadius(Number(
5846
(divStyle.borderRadius || divStyle.borderTopLeftRadius)
5947
.replace('px', ''))
6048
)
61-
setBackground(getColor(
49+
states.setBackground(getColor(
6250
getNthStyleAttrOrAlt('background', 'background-color', 1)
6351
) || 'transparent')
64-
setBackgroundImage(getImage(
52+
states.setBackgroundImage(getImage(
6553
getNthStyleAttrOrAlt('background', 'background-image', 1)
6654
) || 'none')
67-
setBackgroundImageSize(getImageSize(
55+
states.setBackgroundImageSize(getImageSize(
6856
getNthStyleAttr('background-size', 1)
69-
) || null)
70-
setBackgroundOpacity(getOpacity(
57+
) || [null, null])
58+
states.setBackgroundOpacity(getOpacity(
7159
getNthStyleAttrOrAlt('background', 'background-color', 1)
7260
) || 1)
61+
states.setBackgroundRepeat(getRepeat(
62+
getNthStyleAttrOrAlt('background', 'background-repeat', 1)
63+
) || ['repeat', 'repeat'])
7364

74-
setBorderColor(getColor(
65+
states.setBorderColor(getColor(
7566
getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1)
7667
) || 'transparent')
77-
setBorderOpacity(getOpacity(
68+
states.setBorderOpacity(getOpacity(
7869
getNthStyleAttrOrAlt('border', 'border-color', 'border-top-color', 1)
7970
) || 1)
8071

81-
setBorderWidth(getWidth(
72+
states.setBorderWidth(getWidth(
8273
getNthStyleAttrOrAlt('border', 'border-width', 'border-top-width', 0),
8374
div.current
8475
) || 0)

0 commit comments

Comments
 (0)