mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-08-15 18:45:06 +00:00
[change] Image: improve tintColor style implementation
The `tintColor` style is now implemented by passing an inline SVG color matrix as the CSS `filter`. This avoids the need to render SVG elements to the DOM and avoids the need to manage unique IDs. Still no support in Safari; lacks support for inline SVG filters. Close #1914
This commit is contained in:
@@ -98,6 +98,17 @@ export default function ImagePage() {
|
||||
<Image resizeMode="stretch" source={resizesource} style={styles.resizeMode} />
|
||||
</View>
|
||||
</View>
|
||||
<Divider />
|
||||
<View style={styles.row}>
|
||||
<View style={styles.column}>
|
||||
<Text style={styles.text}>Tint color</Text>
|
||||
<Image source={dataBase64Svg} style={[styles.image, { tintColor: 'red' }]} />
|
||||
</View>
|
||||
<View style={styles.column}>
|
||||
<Text style={styles.text}>Tint color</Text>
|
||||
<Image source={dataSvg} style={[styles.image, { tintColor: 'blue' }]} />
|
||||
</View>
|
||||
</View>
|
||||
</Example>
|
||||
);
|
||||
}
|
||||
|
||||
+2
-36
@@ -329,25 +329,8 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`]
|
||||
>
|
||||
<div
|
||||
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
|
||||
style="filter: url(#tint-57);"
|
||||
style="filter: url('data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\"><filter id=\\"tint\\"><feColorMatrix type=\\"matrix\\" values=\\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1.00 0\\" /></filter></svg>#tint');"
|
||||
/>
|
||||
<svg
|
||||
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
|
||||
>
|
||||
<defs>
|
||||
<filter
|
||||
id="tint-57"
|
||||
>
|
||||
<feflood
|
||||
flood-color="blue"
|
||||
/>
|
||||
<fecomposite
|
||||
in2="SourceAlpha"
|
||||
operator="atop"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -378,7 +361,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
|
||||
>
|
||||
<div
|
||||
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
|
||||
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-56);"
|
||||
style="background-image: url(https://google.com/favicon.ico); filter: url('data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\"><filter id=\\"tint\\"><feColorMatrix type=\\"matrix\\" values=\\"0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1.00 0\\" /></filter></svg>#tint');"
|
||||
/>
|
||||
<img
|
||||
alt=""
|
||||
@@ -386,23 +369,6 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
|
||||
draggable="false"
|
||||
src="https://google.com/favicon.ico"
|
||||
/>
|
||||
<svg
|
||||
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
|
||||
>
|
||||
<defs>
|
||||
<filter
|
||||
id="tint-56"
|
||||
>
|
||||
<feflood
|
||||
flood-color="red"
|
||||
/>
|
||||
<fecomposite
|
||||
in2="SourceAlpha"
|
||||
operator="atop"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
+24
-26
@@ -20,6 +20,7 @@ import PixelRatio from '../PixelRatio';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import TextAncestorContext from '../Text/TextAncestorContext';
|
||||
import View from '../View';
|
||||
import processColor from '../processColor';
|
||||
|
||||
export type { ImageProps };
|
||||
|
||||
@@ -28,23 +29,9 @@ const LOADED = 'LOADED';
|
||||
const LOADING = 'LOADING';
|
||||
const IDLE = 'IDLE';
|
||||
|
||||
let _filterId = 0;
|
||||
const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/;
|
||||
|
||||
function createTintColorSVG(tintColor, id) {
|
||||
return tintColor && id != null ? (
|
||||
<svg style={{ position: 'absolute', height: 0, visibility: 'hidden', width: 0 }}>
|
||||
<defs>
|
||||
<filter id={`tint-${id}`} suppressHydrationWarning={true}>
|
||||
<feFlood floodColor={`${tintColor}`} key={tintColor} />
|
||||
<feComposite in2="SourceAlpha" operator="atop" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
) : null;
|
||||
}
|
||||
|
||||
function getFlatStyle(style, blurRadius, filterId) {
|
||||
function getFlatStyle(style, blurRadius) {
|
||||
const flatStyle = { ...StyleSheet.flatten(style) };
|
||||
const { filter, resizeMode, shadowOffset, tintColor } = flatStyle;
|
||||
|
||||
@@ -65,8 +52,26 @@ function getFlatStyle(style, blurRadius, filterId) {
|
||||
filters.push(`drop-shadow(${shadowString})`);
|
||||
}
|
||||
}
|
||||
if (tintColor && filterId != null) {
|
||||
filters.push(`url(#tint-${filterId})`);
|
||||
if (tintColor) {
|
||||
const colorInt = processColor(tintColor);
|
||||
if (colorInt != null) {
|
||||
const r = (colorInt >> 16) & 255;
|
||||
const g = (colorInt >> 8) & 255;
|
||||
const b = colorInt & 255;
|
||||
const a = ((colorInt >> 24) & 255) / 255;
|
||||
const alpha = a.toFixed(2);
|
||||
const matrix = `0 0 0 0 ${r / 255} 0 0 0 0 ${g / 255} 0 0 0 0 ${b / 255} 0 0 0 ${alpha} 0`;
|
||||
// NOTE: Safari doesn't support inline SVG filters (reported 2012)
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=104169
|
||||
const svgFilter =
|
||||
"url('data:image/svg+xml," +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg">' +
|
||||
'<filter id="tint">' +
|
||||
`<feColorMatrix type="matrix" values="${matrix}" />` +
|
||||
'</filter>' +
|
||||
"</svg>#tint')";
|
||||
filters.push(svgFilter);
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.length > 0) {
|
||||
@@ -85,7 +90,7 @@ function getFlatStyle(style, blurRadius, filterId) {
|
||||
delete flatStyle.overlayColor;
|
||||
delete flatStyle.resizeMode;
|
||||
|
||||
return [flatStyle, resizeMode, _filter, tintColor];
|
||||
return [flatStyle, resizeMode, _filter];
|
||||
}
|
||||
|
||||
function resolveAssetDimensions(source) {
|
||||
@@ -182,14 +187,9 @@ const Image: React.AbstractComponent<ImageProps, React.ElementRef<typeof View>>
|
||||
const [layout, updateLayout] = React.useState({});
|
||||
const hasTextAncestor = React.useContext(TextAncestorContext);
|
||||
const hiddenImageRef = React.useRef(null);
|
||||
const filterRef = React.useRef(_filterId++);
|
||||
const requestRef = React.useRef(null);
|
||||
const shouldDisplaySource = state === LOADED || (state === LOADING && defaultSource == null);
|
||||
const [flatStyle, _resizeMode, filter, tintColor] = getFlatStyle(
|
||||
style,
|
||||
blurRadius,
|
||||
filterRef.current
|
||||
);
|
||||
const [flatStyle, _resizeMode, filter] = getFlatStyle(style, blurRadius);
|
||||
const resizeMode = props.resizeMode || _resizeMode || 'cover';
|
||||
const selectedSource = shouldDisplaySource ? source : defaultSource;
|
||||
const displayImageUri = resolveAssetUri(selectedSource);
|
||||
@@ -293,10 +293,8 @@ const Image: React.AbstractComponent<ImageProps, React.ElementRef<typeof View>>
|
||||
{ backgroundImage, filter },
|
||||
backgroundSize != null && { backgroundSize }
|
||||
]}
|
||||
suppressHydrationWarning={true}
|
||||
/>
|
||||
{hiddenImage}
|
||||
{createTintColorSVG(tintColor, filterRef.current)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user