Как я могу автоматически масштабировать элемент SVG в React Native View?

Я пытаюсь поставить react-native-svg элемент внутри View так, чтобы он отображался с определенным фиксированным соотношением сторон, но затем масштабировался до максимально возможного в пределах границ содержащего представления.

Svg элемент (от react-native-svgкажется, принимает только абсолют width а также height атрибуты (я пробовал использовать проценты, но ничего не рендерится, и отладка подтверждает, что значения процентов NSNull к тому времени они дойдут до родного вида). Я не уверен, как добиться желаемого эффекта. Вот что я пробовал до сих пор:

// I have a component defined like this:
export default class MySvgCircle extends React.Component {
    render() {
        return (
            <View style={[this.props.style, {alignItems: 'center', justifyContent: 'center'}]} ref="containingView">
                <View style={{flex: 1, justifyContent: 'center', alignItems: 'center', aspectRatio: 1.0}}>
                    <Svg.Svg width="100" height="100">
                        <Svg.Circle cx="50" cy="50" r="40" stroke="blue" strokeWidth="1.0" fill="transparent" />
                        <Svg.Circle cx="50" cy="50" r="37" stroke="red" strokeWidth="6.0" fill="transparent" />
                    </Svg.Svg>
                </View>
            </View>
        );
    }
}

// And then consumed like this:
<MySvgCircle style={{height: 200, width: 200, backgroundColor: "powderblue"}}/>

И это то, что я вижу, когда это делает.

это то, что я вижу

Я хочу, чтобы красные и синие кружки были увеличены, чтобы заполнить область 200x200 (оставаясь круглыми, если вмещающий вид является прямоугольным, а не квадратным), без предварительного знания размера, требуемого потребителем / пользователем компонента.

Как уже упоминалось, я попытался использовать проценты, как это (остальное то же самое):

<Svg.Svg width="100%" height="100%">

Но тогда часть SVG вообще не рисует. Как это:

нет рендеринга SVG

В журналах консоли нет сообщений об ошибках или других признаков того, почему это не работает.

Методы измерения элементов пользовательского интерфейса после размещения в RN кажутся асинхронными, что кажется плохим соответствием тому, что я пытаюсь сделать. Могу ли я использовать магию масштабирования или трансформации?

Желаемый результат будет выглядеть следующим образом (получен путем жесткого кодирования):

желаемый результат

И когда вмещающий вид не является идеальным квадратом, я бы хотел, чтобы он работал так:

поведение горизонтального прямоугольника поведение вертикального прямоугольника

8 ответов

Вот компонент, который ведет себя как ваши изображения:

import React from 'react';
import { View } from 'react-native';

import Svg, { Circle } from 'react-native-svg';

const WrappedSvg = () =>
  (
    <View style={{ aspectRatio: 1, backgroundColor: 'blue' }}>
      <Svg height="100%" width="100%" viewBox="0 0 100 100">
        <Circle r="50" cx="50" cy="50" fill="red" />
      </Svg>
    </View>
  );

В контексте:

const WrappedSvgTest = () => (
  <View>
    <View style={{
      width: '100%',
      height: 140,
      alignItems: 'center',
      backgroundColor: '#eeeeee'
    }}
    >
      <WrappedSvg />
    </View>

    {/* spacer */}
    <View style={{ height: 100 }} />

    <View style={{
      width: 120,
      height: 280,
      justifyContent: 'space-around',
      backgroundColor: '#eeeeee'
    }}
    >
      <WrappedSvg />
    </View>
  </View>
);

Хитрость заключается в том, чтобы обернуть элемент SVG в представление, которое сохраняет его соотношение сторон, а затем установить размер SVG на 100% ширины и высоты.

Я полагаю, что между размером элемента SVG и размером окна просмотра существует некоторое сложное взаимодействие, из-за которого SVG-рендеринг меньше, чем вы ожидаете, а в некоторых случаях вообще не визуализирует. Вы можете избежать этого, сохранив свой <View> теги с фиксированным соотношением сторон и установка <Svg> теги шириной и высотой 100%, поэтому соотношение сторон окна просмотра всегда соответствует соотношению элементов.

Обязательно установите aspectRatio в viewbox.width / viewbox.height,

Трюк в

  preserveAspectRatio="xMinYMin slice"

ты должен сделать это

<Svg 
 height="100%" 
 preserveAspectRatio="xMinYMin slice" 
 width="100%" 
 viewBox="0 0 100 100"
>


Вы должны играть с шириной и высотой вместе с viewBox. Обычно в viewBox вам нужно разместить исходные размеры желаемой формы. А определяя ширину / высоту в зависимости от ваших потребностей, ваша фигура будет правильно масштабироваться.

Пожалуйста, взгляните на этот урок, где эти понятия были объяснены довольно ясно.

https://www.sarasoueidan.com/blog/svg-coordinate-systems/

Для моего SVG я использовал те, которые предоставлены на https://material.io/resources/icons

Что исправило для меня, так это убедиться, что вы не возитесь с viewBox или заданные значения в Paths (как и я), но измените только height а также width для заполнения, а затем использовать контейнеры, как и другие ответы:

<View style={{
    height: 100, display: 'flex',
}}>
    <TouchableOpacity style={{
        display: 'flex', justifyContent: 'center',
        alignItems: 'center', aspectRatio: 1,
    }}>
        <Svg fill="white" height="100%"
             width="100%" viewBox="0 0 24 24">
            <Path d="M0 0h24v24H0z" fill="none"/>
            <Path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
        </Svg>
    </TouchableOpacity>
</View>

Вам понадобятся эти переменные

      const originalWidth = 744; 
const originalHeight = 539.286; 
const aspectRatio = originalWidth / originalHeight;

Оберните свой svg в виде со следующими свойствами:

      <View style={{ width: '100%', aspectRatio }}></View>

или

      <View style={{ width: Dimensions.get('window').width, aspectRatio }}>
</View>

Используйте svg внутри со следующими свойствами:

      <Svg
  width='100%'
  height='100%'
  viewBox={`0 0 ${originalWidth} ${originalHeight}`}
>

И ты должен быть в порядке!

Я положил все это дело в пример Snack, может быть, это поможет.

ЗАКУСКА: https://snack.expo.dev/@changnoi69/fbf937

Когда вы меняете marginLeft и marginRight этого представления, которое обернуто вокруг SVG-компонента, размер SVG изменяется в соответствии с ним.

      

 <View style={{marginLeft:"20%", marginRight:"20%", backgroundColor: "pink"}}> 
    <NoInternetConnectionSVG />
    </View>

Оригинальный пост Stackoverflow находится здесь: https://stackoverflow.com/a/73511233/12647753

я использую react-native-svg-transformer без использования react-native-svg, который я нашел очень тяжелым с точки зрения размера, поэтому я могу изменить размер и изменить цвет обводки, а также цвет заливки, но только вместо передачи реквизита заливки , просто передайте цвет, как показано ниже, он отлично работает

      import React from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
} from 'react-native';
import { StatusBar } from 'expo-status-bar';
import Logo from "../../assets/profile.svg";

function FirstScreen(props) {
  return (
    <View style={styles.container}>
      <TouchableOpacity
        onPress={() => { props.navigation.navigate('SecondScreen'); }}
      >
        <Text>Welcome</Text>
        <View style={{ aspectRatio: 1,justifyContent:"center",alignItems:"center", backgroundColor: 'blue',width:200,height:200 }}>
        <Logo color="white" stroke="black" height={50} width={50} />
        </View>
      </TouchableOpacity>
      <StatusBar style="auto" />
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});
export default FirstScreen;

SVG-код

      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><title>profile</title><g fill="currentColor" class="nc-icon-wrapper"><path d="M38,37H26A19.021,19.021,0,0,0,7,56a1,1,0,0,0,.594.914C7.97,57.081,16.961,61,32,61s24.03-3.919,24.406-4.086A1,1,0,0,0,57,56,19.021,19.021,0,0,0,38,37Z"></path><path data-color="color-2" d="M32,32c8.013,0,14-8.412,14-15.933a14,14,0,1,0-28,0C18,23.588,23.987,32,32,32Z"></path></g></svg>

зависимости

        "dependencies": {
    "@expo/webpack-config": "~0.16.2",
    "@react-navigation/native": "^6.0.10",
    "@react-navigation/native-stack": "^6.6.2",
    "expo": "~45.0.0",
    "expo-font": "^10.1.0",
    "expo-status-bar": "~1.3.0",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-native": "0.68.2",
    "react-native-svg-transformer": "^1.0.0",
  },

файл metro.config.js добавить в корень

      const { getDefaultConfig } = require('expo/metro-config');

module.exports = (() => {
  const config = getDefaultConfig(__dirname);

  const { transformer, resolver } = config;

  config.transformer = {
    ...transformer,
    babelTransformerPath: require.resolve('react-native-svg-transformer'),
  };
  config.resolver = {
    ...resolver,
    assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'),
    sourceExts: [...resolver.sourceExts, 'svg'],
  };

  return config;
})();

В моем случае мне пришлось масштабировать значок SVG в зависимости от размера устройства, и он использовал и<Path>для рисования значка. После нескольких часов проб и ошибок я нашел решение - присвоить значение динамического масштаба (в зависимости от размера устройства) внутреннему компоненту компонента Svg. Здесь внутренняя составляющая<G>

      <Svg width={RfH(24)} height={RfH(24)} style={{backgroundColor: 'salmon'}}>
<G
  scale={RfH(1)} // Scaling added to the inner component
  fill="none"
  fillRule="evenodd">
  <G
    stroke={props?.isFocused ? '#302F4C' : '#8B8B88'}
    strokeLinecap="round"
    strokeLinejoin="round"
    strokeWidth={1.5}>
    <Path
      d="M9.393 2.792 3.63 7.022c-.9.7-1.63 2.19-1.63 3.32v7.41c0 2.32 1.89 4.22 4.21 4.22h11.58c2.32 0 4.21-1.9 4.21-4.21v-7.28c0-1.21-.81-2.76-1.8-3.45l-5.807-4.36c-1.4-.98-3.65-.93-5 .12Z"
      fill={props?.isFocused ? '#7BBDFF' : 'none'}
      fillRule="nonzero"
    />
    <Path fill="#FFF" d="M12 17.993v-2.924" />
  </G>
</G>

- Значок дома iPad с масштабированием

- Главная иконка iPad без масштабирования

- Значок дома iPhone с масштабированием

- Домашняя иконка iPhone без масштабирования

Rfhпросто преобразует входное значение в эквивалент текущего устройства.

      import {Dimensions} from 'react-native';

const STANDARD_SCREEN_DIMENSIONS = {height: 812, width: 375};

const RfH = (value) => {
      const dim = Dimensions.get('window');
      return dim.height * (value / STANDARD_SCREEN_DIMENSIONS.height);
};
Другие вопросы по тегам