如何使用 react-native-svg 找到宽度、高度和 viewBox 的正确值

Lui*_*ano 5 css svg aspect-ratio react-native

我一直在尝试做一些看起来很简单的事情,但我已经尝试了几个小时,但找不到解决方案。

我有一个需要位于屏幕顶部的 SVG。它来自具有以下尺寸的设计师:

<Svg width="354px" height="190px" viewBox="0 0 354 190">...</Svg>
Run Code Online (Sandbox Code Playgroud)

React Native 中,这将进入容器内部,SVG 需要占据屏幕的整个宽度,我从中获取:

Dimensions.get("window").width
Run Code Online (Sandbox Code Playgroud)

我的问题是,我还没有找到一种方法来缩放 SVG 以占据屏幕宽度的 100%,找出正确的高度(或自动设置它的方法),并保留纵横比。我已经尝试了一百万次,包括使用容器的aspectRatio样式及其高度(或根本不设置高度)。每当我发现一些有效的“比例”时,我都会尝试使用不同屏幕宽度的不同设备,但它看起来一点也不好看(裁剪过,小于屏幕宽度等)。

我觉得SVG (react-native-svg) 中的preserveAspectRatio属性在某种程度上与aspectRatio风格相冲突。我完全迷失了preserveAspectRatio,我还没有找到一种方法来使它缩放而不被裁剪。

有谁知道如何实现这一目标?

这是我的最终代码,它返回一个显示 SVG 的 HeatMap 组件,但尽管它具有正确的高度,但 SVG 的一部分从右侧超出了屏幕(看起来被裁剪,因为它太宽了):

const windowWidth = Dimensions.get("window").width;

const getSVGRootProps = ({ width, height }) => ({
  width: "100%",
  height: "100%",
  viewBox: `0 0 ${width} ${height}`,
  preserveAspectRatio: "xMinYMin meet",
});

const FieldShape = () => {
  const width = 354; // Original width
  const height = 190; // Original height
  const aspectRatio = width / height;
  // adjusted height = <screen width> * original height / original width
  const calculatedHeight = (windowWidth * height) / width;
  const fieldStyles = {
    width: windowWidth,
    height: calculatedHeight,
    aspectRatio,
  };

  return (
    <View style={fieldStyles}>
      <Svg {...getSVGRootProps({ windowWidth, calculatedHeight })}>
      ...
      </Svg>
    </View>
  );
};

const HeatMap = () => {
  return <FieldShape />;
};
Run Code Online (Sandbox Code Playgroud)

这是结果:

结果图片(打算像素化)

Lui*_*ano 18

我找到了解决方案,我将它发布在这里,以防有人遇到与 react native 和 SVG 相同的问题。基本上,如果您尝试获取 SVG 文件并将其转换为具有“动态”部分的组件(例如根据数据以编程方式将颜色设置为路径,或显示 SVG 文本),您可能会遇到此问题。

我所做的是使用SVGR将原始 SVG 转换为 react native 组件(使用react-native-svg)。然后我只是根据需要用变量(来自道具)替换了硬编码数据。看起来不错,但我对组件的大小有疑问。我找不到在不同设备尺寸和分辨率下显示它的一致方式。这看起来很简单,但我尝试了几个小时,结果在每个屏幕尺寸上都不同。在此处询问并在 react-native-svg repo 上打开一个问题后,我没有得到答案也没有任何线索(不是责怪任何人,只是说这可能不是很多人遇到的问题)。所以我挖又挖,我终于找到了了 Lea Verou 的这篇文章,她谈到了绝对和相对 SVG 路径。这让我想到,也许我在寻找完美的调整大小公式时遇到了很多问题,因为我的路径不是相对的,而是绝对的。所以我通过 heyzeuss尝试了这个jsfiddle,粘贴我的(原始)SVG 代码,然后复制结果。我将结果粘贴到这个方便的SVGR 游乐场(SVG 到 JS)工具中,然后我更改了一些位以实现我的目标:

  • 我希望我的 SVG 占据全屏的宽度,宽度和高度相应地缩放。

所以这就是我改变的:

// SVG's original size is 519 width, 260 height
// <Svg width="519" height="260" viewBox="0 0 519 260">...</Svg>
// I also added a container, which enforces the aspect ratio
const originalWidth = 519;
const originalHeight = 260;
const aspectRatio = originalWidth / originalHeight;
const windowWidth = Dimensions.get("window").width;
return (
    <View style={{ width: windowWidth, aspectRatio }}>
        <Svg 
            width="100%" 
            height="100%" 
            viewBox={`0 0 ${originalWidth} ${originalHeight}`}>
        ...
        </Svg>
    </View>
)
Run Code Online (Sandbox Code Playgroud)

我在这样做的过程中学到了一些东西,例如,create-react-app 中包含一个@svgr/cli,并且在我的 react-native 项目中也可以使用,而无需安装任何额外的东西,因此它也必须与原始依赖项捆绑在一起。你可以运行这个命令,它会将单个文件或文件夹中的所有文件从 .svg 转换为 React 组件:

npx @svgr/cli [-d out-dir] [--ignore-existing] [src-dir]
Run Code Online (Sandbox Code Playgroud)

用于将绝对路径转换为相对路径的脚本是这个名为Snap.svg 的库的一部分,但您只需要其中的 1% ( Snap.path.toRelative )。我正在考虑使用一个小工具来获取 svg 文件中的所有路径,并应用此转换。老实说,我多年来一直在处理 SVG 文件,但我从来没有真正了解过它的内部工作方式、路径格式、坐标等,所以这很有启发性:)

如果您发现自己处于相同的情况,我希望这对您有所帮助!

  • 刚刚遇到这个问题,您的解决方案完美运行。感谢您投入时间! (3认同)

小智 6

扩展 Luis Serrano 的答案,您可以省略windowWidth并使用100%

例子:

import * as React from 'react';
import Svg, { Circle, Path } from 'react-native-svg';
import { View } from 'react-native';

export default function SvgExample() {

  const originalWidth = 500;
  const originalHeight = 500;
  const aspectRatio = originalWidth / originalHeight;

  return (
    <View style={{ width: "100%", aspectRatio, backgroundColor: "salmon" }}>
      <Svg width="100%" height="100%" viewBox={`0 0 ${originalWidth} ${originalHeight}`}>
        <Circle cx="250" cy="250" r="40" fill="yellow" />
        <Circle cx="240" cy="240" r="4" fill="black" />
        <Circle cx="260" cy="240" r="4" fill="black" />
        <Path d="M 240 265 A 20 20 0 0 0 260 260" strokeWidth={2} stroke="black" />
      </Svg>
    </View>
  )
}
Run Code Online (Sandbox Code Playgroud)

这样做的优点是它尊重封闭视图的填充。

import { StyleSheet, View } from 'react-native';
import SvgExample from './SvgExample';

export default function TabOneScreen() {
  return (
    <View style={styles.container}>
      <SvgExample/>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    padding: 20,
  },
});
Run Code Online (Sandbox Code Playgroud)