feat: support filters on web (#2346)

# Summary

* Export `Filter` and `FeColorMatrix` components on `web`
* Change filter IDs in example to be unique*
* Generate filter ID when using `FilterImage`*
* Hide `FilterImage` example on web, since it's crashing the whole site
(see #2334)

\* ID on web has to be unique, otherwise it'll use the first element
with that ID, even if they are in the separate SVG elements

## Test Plan

run `web-example` app

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| Web     |         |
This commit is contained in:
Jakub Grzywacz
2024-07-25 10:45:24 +02:00
committed by GitHub
parent 52466a2564
commit ba7d77548f
4 changed files with 39 additions and 18 deletions

View File

@@ -18,10 +18,10 @@ class IdentityExample extends Component {
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<Filter id="filterIdentity">
<FeColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
</Filter>
<G filter="url(#filter)">
<G filter="url(#filterIdentity)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
@@ -35,7 +35,7 @@ class RgbToGreenExample extends Component {
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<Filter id="filterGreen">
<FeColorMatrix
values="0 0 0 0 0
1 1 1 1 0
@@ -43,7 +43,7 @@ class RgbToGreenExample extends Component {
0 0 0 1 0"
/>
</Filter>
<G filter="url(#filter)">
<G filter="url(#filterGreen)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
@@ -57,10 +57,10 @@ class SaturateExample extends Component {
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<Filter id="filterSaturate">
<FeColorMatrix type="saturate" values="0.2" />
</Filter>
<G filter="url(#filter)">
<G filter="url(#filterSaturate)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
@@ -75,10 +75,10 @@ class HueRotateExample extends Component {
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<Filter id="filterHue">
<FeColorMatrix type="hueRotate" values="180" />
</Filter>
<G filter="url(#filter)">
<G filter="url(#filterHue)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
@@ -93,10 +93,10 @@ class LuminanceToAlphaExample extends Component {
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<Filter id="filterLuminance">
<FeColorMatrix type="luminanceToAlpha" />
</Filter>
<G filter="url(#filter)">
<G filter="url(#filterLuminance)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
@@ -108,10 +108,10 @@ class LuminanceToAlphaExample extends Component {
const icon = (
<Svg height="30" width="30" viewBox="0 0 20 20">
<Filter id="filter">
<Filter id="filterIcon">
<FeColorMatrix values="0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0" />
</Filter>
<G filter="url(#filter)">
<G filter="url(#filterIcon)">
<Circle cx="10" cy="7.5" r="5" fill="blue" fillOpacity="0.5" />
<Circle cx="7.5" cy="12.5" r="5" fill="green" fillOpacity="0.5" />
<Circle cx="12.5" cy="12.5" r="5" fill="red" fillOpacity="0.5" />

View File

@@ -22,6 +22,8 @@ import type { TextProps } from './elements/Text';
import type { TextPathProps } from './elements/TextPath';
import type { TSpanProps } from './elements/TSpan';
import type { UseProps } from './elements/Use';
import type { FilterProps } from './elements/filters/Filter';
import type { FeColorMatrixProps } from './elements/filters/FeColorMatrix';
import type {
GestureResponderEvent,
TransformsStyle,
@@ -576,4 +578,12 @@ export class Pattern extends WebShape<BaseProps & PatternProps> {
tag = 'pattern' as const;
}
export class Filter extends WebShape<BaseProps & FilterProps> {
tag = 'filter' as const;
}
export class FeColorMatrix extends WebShape<BaseProps & FeColorMatrixProps> {
tag = 'feColorMatrix' as const;
}
export default Svg;

View File

@@ -1,18 +1,22 @@
import { useMemo } from 'react';
import {
ImageProps,
ImageStyle,
Platform,
Image as RNImage,
StyleProp,
StyleSheet,
View,
} from 'react-native';
import { Filter, Image, Svg } from '../index';
import { extractResizeMode } from './extract/extractImage';
import { Filters } from './types';
import { resolveAssetUri } from '../lib/resolveAssetUri';
import { getRandomNumber } from '../lib/util';
import {
extractFiltersCss,
mapFilterToComponent,
} from './extract/extractFilters';
import { extractResizeMode } from './extract/extractImage';
import { Filters } from './types';
export interface FilterImageProps extends ImageProps {
style?: StyleProp<ImageStyle & { filter?: string | Filters }> | undefined;
@@ -23,10 +27,14 @@ export const FilterImage = (props: FilterImageProps) => {
const { filters = [], source, style, ...imageProps } = props;
const styles = StyleSheet.flatten(style);
const extractedFilters = [...filters, ...extractFiltersCss(styles?.filter)];
const filterId = useMemo(() => `RNSVG-${getRandomNumber()}`, []);
const src = RNImage.resolveAssetSource(source);
const width = props.width || styles?.width || src.width;
const height = props.height || styles?.height || src.height;
const src =
Platform.OS === 'web'
? resolveAssetUri(source)
: RNImage.resolveAssetSource(source);
const width = props.width || styles?.width || src?.width;
const height = props.height || styles?.height || src?.height;
const preserveAspectRatio = extractResizeMode(props.resizeMode);
return (
@@ -41,7 +49,7 @@ export const FilterImage = (props: FilterImageProps) => {
width="100%"
height="100%"
preserveAspectRatio={preserveAspectRatio}
filter={extractedFilters.length > 0 ? 'url(#filter)' : undefined}
filter={extractedFilters.length > 0 ? `url(#${filterId})` : undefined}
/>
</Svg>
</View>

View File

@@ -12,3 +12,6 @@ export function pickNotNil(object: { [prop: string]: unknown }) {
}
export const idPattern = /#([^)]+)\)?$/;
export const getRandomNumber = () =>
Math.floor(Math.random() * Math.floor(Math.random() * Date.now()));