I have created this codesandbox to illustrate the problem.
I have this ResponsiveSVG component:
export function ResponsiveSVG({
height,
width,
children,
origin = { x: 0, y: 0 },
preserveAspectRatio = "xMidYMid meet",
innerRef,
className,
...props
}: ResponsiveSVGProps): JSX.Element {
const aspect = height === 0 ? 1 : width / height;
const adjustedHeight = Math.ceil(width / aspect);
return (
<div
data-testid="cutting-svg-container"
style={{
position: 'relative',
overflow: 'visible',
height: '1px',
}}
>
<svg
style={{ overflow: 'visible' }}
className={className}
preserveAspectRatio={preserveAspectRatio}
width={width}
height={adjustedHeight}
viewBox={`${origin.x} ${origin.y} ${width} ${height}`}
ref={innerRef}
{...props}
>
{children}
</svg>
</div>
);
}
I would like my ResponsiveSVG component to know how to fit its content to the full width of the container while keeping the aspect ratio the same.
In the example I have a circle as a child of the svg document:
<ResponsiveSVG width={width} height={height}>
<circle cx={width / 2} cy={height / 2} r={radius} />
</ResponsiveSVG>
On desktop, it looks like this:
But in mobile view, it looks like this:
My calculations make the viewBox width and height the same as the actual width and height I am passing in, so no change happens.
The viewBox coordinates are exactly the same as the viewport.
Passing the width and height of the containing element to the viewBox is actually the wrong direction. viewBox defines the canvas on which the svg content elements are drawn: if you draw a circle with r="50", the viewBox width value must be at least 100, otherwise, it will never fit. If the center is at cx=50, the viewBox x value must not be greater than 0, otherwise the left side is cut off.
Responsiveness in SVG is achieved because there needs not to be any relation between the viewBox values and the width and height of the <svg> element. The canvas the child elements are drawn on is always fitted to the parent element dimensions.
In short, if all you want to achieve is to fit a circle in a <svg> element, choose a arbitrary radius r, set cx=cy=r and viewBox="0 0 2r 2r", and it will work. There is no need to know anything explicit about the parent size.
If you want your <svg> element to be the width of the container, set it so: width: 100%. The default height is auto, so no need to write that or preserveAspectRatio. It will size itself such that the canvas defined by viewBox fits itself.
<div
style="
position: relative;
overflow: visible;
height: 1px;
"
>
<svg
style="
width: 100%;
overflow: visible;
"
viewBox="0 0 100 100"
>
<circle cx="50" cy="50" r="50" />
</svg>
</div>
Related
I'm looking to stretch my SVG image along the horizontal axis, which is acting as a background for some HTML divs. It is a simple bar that takes up the middle third horizontally :
It must not move on the vertical, and when horizontal is stretched, the image also must stretch so that the bar takes up the middle third.
Currently if I stretch horizontally, it maintains its vertical position which is good, but the SVG does not take up a third but maintains its original width:
Here is the React code :
function BackgroundSVG() {
let outerDiv = {
position:"relative",
border: "3px solid red",
}
let svgStyle = {
position:"absolute",
top:"0px",
border: "3px solid pink",
zIndex: -1,
width: "100%",
height: "200px"
}
return (
<div style={outerDiv}>
<div>Lorem ipsum .......</div>
<svg style={svgStyle}
viewBox={`0 0 ${600} ${200} `}
xmlns="http://www.w3.org/2000/svg">
<path d={`M ${200} ${100} H ${400}`} strokeWidth={12} stroke="blue"/>
</svg>
</div>
);
}
Here is a code pen
https://codepen.io/oliverwatkins/pen/MWqWmQz
How do I stretch the image along the x-axis, while preserving things on the y-axis?
I have tried to play around with the widths and heights but that does not seem to work. I assume that if I keep width at 100% and height at a 200px that this would solve the problem.
Default preserveAspectRatio attribute value is xMidYMid meet
so that browser try to maintain image aspect ratio.
For stretching SVG image, we need to set
preserveAspectRatio="none"
I have a question regarding the three-fiber canvas size. I need a fixed canvas width and height. Unfortunately I have not found out how to change the canvas width and height. Even though I can change the sizes with style={{width: 100px, height: 100px}}, it is not the right thing. The canvas should be bigger than the div. does anyone have an idea how to do this with three-fiber?
<Canvas
size={[`2000px`,`3000px`]}
style={{width: `100%`, height: `auto`, position: `relative` }}
ref={canvas}
shadows
camera={{position: [10, 0, 80], fov: 45}}
>
<Suspense fallback={false}>
<Content/>
</Suspense>
</Canvas>
React Three Fiber's canvas will take the width and height of the parent container.
Instead of setting the width and height on the Canvas component, try setting the width and height on the parent instead, like this:
<div style={{ width: "50vw", height: "50vh" }}>
<Canvas flat linear>
<App />
</Canvas>
</div>
View Demo
Canvas in three-fiber takes on the width of its parent container. just resize whatever div it is being contained by.
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>
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.
I'm trying to position a custom path underneath images that are being loaded, the images will have the same width but the height will vary.
I'm using react-measure to get width, height of my image in svg to later on use that height to position the next element, but due to presumably aspect ratio, I get wrong values.
<svg viewBox="0 0 1000 2000">
<svg
x="150"
y="200"
>
<Measure
bounds
onResize={ (contentRef) => {
this.setState(prevState => ({ imgDimensions: contentRef.bounds }));
} }
>
{ ({ measureRef }) => (
<svg x="0">
<image className={ classes.img } ref={ measureRef } width="200" xlinkHref={ image } />
</svg>
) }
</Measure>
<rect x="5" y={ this.state.imgDimensions.height } height="2" width={ width } />
</svg>
</svg>
Example values:
imgDimensions:
bottom: 504.6000061035156
height: 176.39999389648438
left: 595
right: 835
top: 328.20001220703125
width: 240
and how it's positioned:
https://imgur.com/a/o4bbFG2
That black line should be exactly where the image ends.
But if I resize the website and refresh it - this is what I get: https://imgur.com/a/n9sxTic
My configuration goes as follows:
// Calling it
<div style={{ width: 1000, height: 800 }}>
<MyComponent />
</div>
// MyComponent.js
<InfiniteLoader { ...itsProps }>
{({ onRowsRendered }) => (
<AutoSizer>
{({ width, height }) => (
<Table
width={ width }
width={ height }
...
The problem is that this is what is being rendered:
<div style="overflow: visible; height: 0px; width: 0px;">
<div class="ReactVirtualized__Table" role="grid">
...
So nothing is seen in the screen.
Maybe I misunderstood Autosizer usage, so I set it up to adjust itself to the parent's width/height.
What am I missing?
The height: 0 style in the snippet of HTML you shared isn't actually a problem, because of the overflow: visible style. This is the way AutoSizer is meant to work.
I suggest you read the "Using AutoSizer" docs page. I suspect your problem is similar to one of the common issues mentioned there:
AutoSizer expands to fill its parent but it will not stretch the parent. This is done to prevent problems with flexbox layouts. If AutoSizer is reporting a height (or width) of 0- then it's likely that the parent element (or one of its parents) has a height of 0. One easy way to test this is to add a style property (eg background-color: red;) to the parent to ensure that it is the correct size. (eg You may need to add height: 100% or flex: 1 to the parent.)