Search code examples
reactjssvggsap

SVG Image viewbox resizing


I have this code that you can run below in the snippet. My issue is that in my production environment I have 2 SVGs, one on top of the other, each containing all sorts of content. When the user clicks on either side of the header, as seen in the snippet, the SVG that is on top should grow or shrink to either show more or less of the SVG underneath (I know this isn't perhaps performant with regards to the SVG potentially being drawn beneath - but my question likely involves this, as the answer probably has something to do with a lack of understanding of SVG viewbox on my part).

The issue is that because I want the images contents to remain the same size as the SVG on top grows or shrinks I decided to use Greensock in order to animate the grow/shrink of both the width AND the viewbox. This though causes very nasty glitching - in the snippet, run the code and click the Black box (pay attention especially to the bottom left corner of the image, but also to the jumps in the leftmost circle)!

Am I mistaken in changing both? Is there a way to only alter the width? Would this still cause the glitch as seen below?

Appreciate your help!

class Header extends React.Component {
  constructor(props) {
    super(props);
    this.leftPanel = null;
    this.leftPanelTween = null;
    this.rightPanel = null;
    this.rightPanelTween = null;
  }
  
  slideLeftHandler = () => {
    this.leftPanelTween = TweenMax.to(this.leftPanel, 2, {width: '100%', attr: {viewBox: "0 0 500 150"}});
  };

  slideRightHandler = () => {
    this.rightPanelTween = TweenMax.to(this.leftPanel, 2, {width: 0, attr: {viewBox: "0 0 0 150"}});
  };
  
  render() {
    return (
      <div class="Header">
        <svg width="100%" viewBox="0 0 500 150" ref={el => this.rightPanel = el} onClick={this.slideRightHandler}>
          <rect id = "middle" width="100%" height="100%" fill="black">
          </rect>
          <circle cx="400" cy="75" r="25" fill="red">
          </circle>
          <circle cx="100" cy="75" r="25" fill="red">
          </circle>
        </svg>
        <svg width="50%" viewBox="0 0 250 150" ref={el => this.leftPanel = el} onClick={this.slideLeftHandler}>
          <rect id = "middle" width="100%" height="100%" fill="red">
          </rect>
          <circle cx="100" cy="75" r="25" fill="black">
          </circle>
          <circle cx="400" cy="75" r="25" fill="black">
          </circle>
        </svg>
      </div>
    );
  }
}


ReactDOM.render(
    <Header />,
  document.getElementById('app')
);
.Header {
    margin: 0;
    cursor: pointer;
}

.Header svg {
    position: absolute;
}

.Header .leftPanel {
    top: 0;
    left: 0;
}

.Header .rightPanel {
    top: 0;
    right: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.2/TweenMax.min.js"></script>

<div id="app"></div>


Solution

  • Animating the viewBox is not performant. I recommend making the SVG as large as it needs to be at the end all of the time and just animate the width of the rectangle within the SVG that was clicked.