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

How to find correct values for width, height and viewBox with react-native-svg

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

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

<Svg width="354px" height="190px" viewBox="0 0 354 190">...</Svg>

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

Dimensions.get("window").width

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

我觉得 SVG (react-native-svg) 中的 preserveAspectRatio 属性 与 aspectRatio[=40= 有点冲突] 风格。我完全迷失了 preserveAspectRatio,我还没有找到一种方法来使其缩放而不被裁剪。

有人知道如何实现吗?

这是我的最终代码,returns 一个显示 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 />;
};

这是结果:

我找到了解决方案,我将其发布在这里,以防有人 运行 遇到与本机反应和 SVG 相同的问题。基本上,如果您尝试获取 SVG 文件并将其转换为具有 "dynamic" 部分的组件(例如以编程方式根据数据将颜色设置为路径,或显示 SVG 文本),您可能会 运行 进入这个问题。

我所做的是使用SVGR to convert the original SVG into a react native component (with react-native-svg). Then I just replaced hardcoded data with variables (from props) as needed. It looked good, but I had a problem with the component's size. I couldn't find a consistent way to display it across different device sizes and resolutions. It seemed easy, but I tried for hours and the results were different on each screen size. After asking here and opening an issue on react-native-svg repo, I got no answers and no clues (not blaming anyone, just saying it was maybe not something a lot of people runs into). So I digged and digged and I finally found this post by Lea Verou, where she talked about absolute and relative SVG paths. That made me think that maybe I was having so many issues trying to find the perfect resizing formula because my paths weren't relative, but absolute. So I tried this jsfiddle by heyzeuss, pasting my (original) SVG code, and then copying the results. I pasted the results into this handy SVGR playground (SVG to JS) tool,然后我改变了一些位来实现我的目标:

  • 我希望我的 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>
)

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

npx @svgr/cli [-d out-dir] [--ignore-existing] [src-dir]

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

如果您遇到同样的情况,希望这对您有所帮助!

扩展 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>
  )
}

这样做的好处是它尊重封闭视图的 填充

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,
  },
});