简体   繁体   English

有没有办法在反应中动态构建 svg ?

[英]Is there a way to dynamically construct an svg in react?

In my web app there is a hamburger making portion.在我的 web 应用程序中有一个汉堡包制作部分。 The user starts off with a hamburger that has nothing but the top bun and the bottom bun.用户从一个只有顶部面包和底部面包的汉堡包开始。 The user can choose from an assortment of "toppings" to add to the hamburger, and the changes are then shown.用户可以从各种各样的“浇头”中选择添加到汉堡包中,然后显示更改。 I have SVG files for the top bun, bottom bun, lettuce, patty, cheese, etc, and I wish to combine them (stack them) together dynamically in run time.我有顶部面包、底部面包、生菜、肉饼、奶酪等的 SVG 文件,我希望在运行时将它们动态组合(堆叠)在一起。 I am currently able to stack them dynamically, but I wish to have certain SVG files overlap others.我目前能够动态堆叠它们,但我希望某些 SVG 文件与其他文件重叠。

I'll show you what I mean我会告诉你我的意思

Step 1步骤1

Above you can see that the hamburger starts off with nothing on it.在上面你可以看到汉堡包开始时没有任何东西。 The react DOM looks something like反应 DOM 看起来像

<img src = {topBunSVG}> </img>
<img src = {buttonBunSVG}> </img>

Let's click on tomato to add it.让我们点击番茄来添加它。

Step 2第2步

Now you can see that the tomato is now between the two buns.现在您可以看到番茄现在位于两个面包之间。 The DOM now looks like this DOM 现在看起来像这样

<img src = {topBunSVG}> </img>
<img src = {tomatoSVG}> </img>
<img src = {buttonBunSVG}> </img>

Same thing with adding a patty添加馅饼也是一样的

Step 3第 3 步

The DOM now looks like this DOM 现在看起来像这样

<img src = {topBunSVG}> </img>
<img src = {pattySVG}> </img>
<img src = {tomatoSVG}> </img>
<img src = {buttonBunSVG}> </img>

Lets try something more complex, like adding some cheese让我们尝试一些更复杂的东西,比如添加一些奶酪

Step 4第4步

You can see that the cheese overlaps the other components您可以看到奶酪与其他组件重叠

The DOM now looks like this DOM 现在看起来像这样

<img src = {topBunSVG}> </img>
<img src = {cheeseSVG}> </img>
<img src = {pattySVG}> </img>
<img src = {tomatoSVG}> </img>
<img src = {buttonBunSVG}> </img>

However, React actually displays it like this:然而,React 实际上是这样显示的:

React Actual反应实际

Which is not what I want.这不是我想要的。

How would I make them stack correctly?我如何让它们正确堆叠?

Also when I make the window smaller it looks like this: Responsive component resized此外,当我使 window 更小时,它看起来像这样: Responsive component resized

Here's the relevant code for the component:这是组件的相关代码:

import React, { useEffect, useReducer, useState } from "react";
import "./makeHamburger.scss";
import bottomBun from "../../assets/foodParts/hamburger/bottombun.svg"
import cheese from "../../assets/foodParts/hamburger/cheese.svg"
import lettuce from "../../assets/foodParts/hamburger/lettuce.svg"
import patty from "../../assets/foodParts/hamburger/patty.svg"
import tomato from "../../assets/foodParts/hamburger/tomato.svg"
import topBun from "../../assets/foodParts/hamburger/topbun.svg"






const parts = {
  topBun: {
    view: topBun,
    price: 1.5,
    macros: {
      carbs: 1,
      protein: 0,
      fiber: 0,
      fat: 0,
    },
  },
  bottomBun: {
    view: bottomBun,
    price: 1.5,
    macros: {
      carbs: 1,
      protein: 0,
      fiber: 0,
      fat: 0,
    },
  },
  patty: {
    view: patty,
    price: 5,
    macros: {
      carbs: 0,
      protein: 1,
      fiber: 0,
      fat: 0,
    },
  },
  cheese: {
    view: cheese,
    price: 1,
    macros: {
      carbs: 0,
      protein: 0,
      fiber: 0,
      fat: 1,
    },
  },
  lettuce: {
    view: lettuce,
    price: 2,
    macros: {
      carbs: 0,
      protein: 0,
      fiber: 1,
      fat: 0,
    },
  },
  tomato: {
    view: tomato,
    price: 2,
    macros: {
      carbs: 0,
      protein: 0,
      fiber: 1,
      fat: 0,
    },
  },
}

const initialState = { contents: [parts.topBun, parts.bottomBun] };

function reducer(state, action) {
  let tmp = JSON.parse(JSON.stringify(state))
  switch (action.type) {
    case "addPatty":

      tmp.contents.splice(1, 0, parts.patty)
      return {
        contents: tmp.contents
      }
      break;
    case "addCheese":

      tmp.contents.splice(1, 0, parts.cheese)
      return {
        contents: tmp.contents
      }
      break;
    case "addLettuce":

      tmp.contents.splice(1, 0, parts.lettuce)
      return {
        contents: tmp.contents
      }
      break;
    case "addTomato":

      tmp.contents.splice(1, 0, parts.tomato)
      return {
        contents: tmp.contents
      }
      break;
    case "deletePartsById":

      if ((action.payload == 0) || (action.payload == (state.contents.length - 1))) {
        return state;
      }
      tmp.contents.splice(action.payload, 1)
      return {
        contents: tmp.contents
      }
      break;

    default:
      break;
  }
}


const MakeHamburger = ({recieveFood}) => {

  const [state, dispatch] = useReducer(reducer, initialState);


  const getTotalPrice = () => {
    let price = 0;
    state.contents.forEach((item,index) => {
      price += item.price
    })

    return price;
  }

  const getTotalCarbs = () => {
    let carbs = 0;
    state.contents.forEach((item,index) => {
      carbs += item.macros.carbs
    })
    return carbs;
  }

  const getTotalFat = () => {
    let fat = 0;
    state.contents.forEach((item,index) => {
      fat += item.macros.fat
    })
    return fat;
  }

  const getTotalFiber = () => {
    let fiber = 0;
    state.contents.forEach((item,index) => {
      fiber += item.macros.fiber
    })
    return fiber;
  }

  const getTotalProtein = () => {
    let protein = 0;
    state.contents.forEach((item,index) => {
      protein += item.macros.protein
    })
    return protein;
  }

  const getTotalMacros = () => {
    return{
      carbs: getTotalCarbs(),
      protein: getTotalProtein(),
      fiber: getTotalFiber(),
      fat: getTotalFat(),
    }
  }

  return (
    <div id="food" className="container">
      <h1>Let's make Max a burger</h1>
      <p>Select what goes on the burger</p>
      <div className="container">
        <div className="row justify-content-center">
          <div className="col-6">
            <div className="row">
              <div className="col">
                <button className="transparentButton" onClick={() => { dispatch({ type: 'addCheese' }) }}>
                  <img className="selectPartImg" src={cheese}></img>
                </button>
              </div>
              <div className="col">
                <p>Cheese</p>
              </div>
            </div>
            <div className="row mt-3">
              <div className="col">
                <button className="transparentButton" onClick={() => { dispatch({ type: 'addLettuce' }) }}>
                  <img className="selectPartImg" src={lettuce}></img>
                </button>
              </div>
              <div className="col">
                <p>Lettuce</p>
              </div>
            </div>
            <div className="row mt-3">
              <div className="col">
                <button className="transparentButton" onClick={() => { dispatch({ type: 'addPatty' }) }}>
                  <img className="selectPartImg" src={patty}></img>
                </button>
              </div>
              <div className="col"><p>Patty</p></div>
            </div>
            <div className="row mt-3">
              <div className="col">
                <button className="transparentButton" onClick={() => { dispatch({ type: 'addTomato' }) }} >
                  <img className="selectPartImg" src={tomato}></img>
                </button>
              </div>
              <div className="col"><p>Tomato</p></div>
            </div>

          </div>
          <div className="col-6 ">
            {state.contents.map(function (part, i) {
              return <div className="row no-gutters">
                <div className="col">
                  <button className="transparentButton" onClick={() => { dispatch({ type: 'deletePartsById', payload: i }) }} >
                    <img className="partImg" src={part.view}></img>
                    
                  </button>
                </div>
              </div>
            })}
            <h1>Total price: {getTotalPrice()}</h1>
            <p>({getTotalCarbs() ? getTotalCarbs() + " carbs" : null}{getTotalFat() ? ", " + getTotalFat() + " fat" : null}
            {getTotalFiber() ? ", " + getTotalFiber() + " fiber" : null}{getTotalProtein() ? ", " + getTotalProtein() + " protein" : null})</p>
            <button onClick = {() => {recieveFood(getTotalMacros())}}>Feed this burger</button>
          </div>
        </div>
      </div>


    </div>
  );
};

export default MakeHamburger;

If the height of one layer is not identical with the height of the image, you need to specify the height of each individual layer.如果某一层的高度与图像的高度不同,则需要指定每个单独层的高度。 (The information about the desired height just does not exist in the image). (图像中不存在有关所需高度的信息)。

(A) You can create SVG data dynamically: (A) 您可以动态创建 SVG 数据:

import React, { useEffect, useState } from 'react';

export const Patties = (props) => {
    return (<>
        { [...props.patties].reverse().map( function(patty, index){
            return <>
                <path d={ 'M 0 ' + (60 - index * 20) + ' h 100 l -50 30 z' } fill={ patty.color } />
            </>
        })}
    </>);
};

export const Hamburger = (props) => {
    return (
        <svg
            xmlns="http://www.w3.org/2000/svg"
            width={ 200 }
            height={ 200 }
            viewBox={ [ 0, 0, 100, 100 ].join(' ') }
        >
            <Patties patties={[
                { color: '#fa0' },
                { color: '#0a0' },
                { color: '#c20' },
            ]}/>
        </svg>
    );
};

(B) You can use absolute positioning: (B) 可以使用绝对定位:

export const Hamburger2 = (props)=>{
    const patties = [
        { url: imgUrlA },
        { url: imgUrlB },
        { url: imgUrlC },
    ];

    return (<div style={{ position: 'relative' }}>
        { patties.reverse().map( function(patty, index){
            return <img
                src={ patty.url }
                style={{ position: 'absolute', top: 60 - index * 20 }}
            />;
        })}
    </div>);
};

Thanks for the suggested answers everyone, I managed to solve the program with this code snippet:感谢大家的建议答案,我设法用这个代码片段解决了这个程序:

    const drawParts = () => {

    

    let parts = [];

    let positionOffSet = 0;

    let z = state.contents.length;

    state.contents.map(function (part, index) {

      parts.push(
        <div className="row no-gutters"  style={{ position: 'relative', zIndex: z, top: positionOffSet }}>
          <div className="col">
            <button className="transparentButton" onClick={() => { dispatch({ type: 'deletePartsById', payload: index }) }} >
              <img className="partImg" src={part.view}></img>
            </button>
          </div>
        </div>
      )
      
      if(part.view == cheese){
        positionOffSet -= 90;
        z--;
      }

      if(part.view == lettuce){
        positionOffSet -= 15;
        z--;
      }

    })

    return parts;
   }

Basically having a global offset and z-value variable that gets incremented everytime a cheese or lettuce shows up.基本上有一个全局偏移量和 z 值变量,每次出现奶酪或生菜时都会增加。 The offset and z-value is applied to all elements.偏移量和 z 值应用于所有元素。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM