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

时间:2021-01-05 16:07:57

标签: javascript reactjs svg

在我的网络应用程序中有一个汉堡包制作部分。用户从一个只有顶部小圆面包和底部小圆面包的汉堡包开始。用户可以从各种“浇头”中进行选择以添加到汉堡包中,然后显示更改。我有顶部面包、底部面包、生菜、肉饼、奶酪等的 SVG 文件,我希望在运行时动态地将它们组合(堆叠)在一起。我目前能够动态堆叠它们,但我希望某些 SVG 文件与其他文件重叠。

我会告诉你我的意思

Step 1

在上面你可以看到汉堡包开始时什么都没有。 反应 DOM 看起来像

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

让我们点击番茄来添加它。

Step 2

现在您可以看到番茄现在位于两个面包之间。 DOM 现在看起来像这样

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

添加馅饼也是一样

Step 3

DOM 现在看起来像这样

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

让我们尝试更复杂的东西,比如加些奶酪

Step 4

你可以看到奶酪与其他成分重叠

DOM 现在看起来像这样

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

然而,React 实际上是这样显示的:

React Actual

这不是我想要的。

如何让它们正确堆叠?

此外,当我将窗口变小时,它看起来像这样: Responsive component resized

这是组件的相关代码:

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;

2 个答案:

答案 0 :(得分:0)

如果某一层的高度与图片的高度不一致,则需要指定每一层的高度。 (图像中不存在有关所需高度的信息)。

(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) 可以使用绝对定位:

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

答案 1 :(得分:0)

感谢大家的建议答案,我设法用这个代码片段解决了这个程序:

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

基本上有一个全局偏移量和 z 值变量,每次出现奶酪或生菜时都会增加。偏移量和 z 值应用于所有元素。