Problem:
I am trying to create a dropdown. When input is focused, the div below it appears. But I expect the div to be at higher z-index. If the div is at higher z-index, then button below it will be covered, but right now div does not take any z-index.
Code:
I have this example on which I am working now:
class Main extends React.Component {
render() {
return( <div style={{width: 200}}>
<input className="input" />
<div className="dropdown" style={{ position: 'absolute', zIndex: 9999 }}></div>
<button>Hello</button>
</div>
)
}
}
ReactDOM.render(
<Main></Main>,
document.getElementById('example')
);
Here is CSS:
html, body {
height: 100%
}
body {
background: #333;
font-family: Helvetica Neue;
}
input {
width: 100%;
}
input + div.dropdown {
background-color: #ffff00;
display: none;
height: 200px;
width: 100%;
}
input:focus + div.dropdown {
display: flex;
}
Here is the plunker of the same code:
https://plnkr.co/edit/B8Kjhv6rUOv0s7QKotxI?p=preview
What I tried:
If I change the position of dropdown div to absolute, then z-index is applied, but the width of the div is equal to the screen width.
I am creating a reusable component, so I can't give fixed width.
Note:
Fixed width given to main div is just an example but in actual use case, the width of main div will be automatically determined by the 100% width of its parent component.
In Summary:
Declare position relative on the containing (parent) element to allow a measure of control over absolutely positioned sibling element's width.
About positioned elements and stacking context:
A few things to consider when dealing with positioned elements and stacking context:
z-index property values will only apply to positioned elements.
positioned elements are elements with position property values defined as absolute, fixed, relative or
stickyexperimental; this does not include static,
the default positioning of any element.
When declaring an element as an absolutely positioned element
(absolute or fixed) you are removing the element from the
natural document flow; which simply means the element is no longer
interacting with sibling elements in the way relative or static
elements do (imagine the element "siting above" the rest of the DOM).
By default, an absolutely positioned element's position is
"relative" to the window; this means if you offset its position
with left or right property values it'll move a distance equal to
the property value from the window. You can position an element
with a position property value of absolute (not fixed) relative
to any containing element if you declare relative positioning to
that containing element.
This was the issue you were observing in your use-case; since the
nested .dropdown element was positioned absolute it was taken out
of the natural flow and occupied the full available width of the
containing document, so in order to restrict it to the width of its
containing element, position: relative should be declared on its
containing element, e.g:
<div style="width:200px;position: relative;" data-reactid=".0">
<input class="input" data-reactid=".0.0">
<div class="dropdown" style="position:absolute;z-index:9999;" data-reactid=".0.1"></div>
<button data-reactid=".0.2">Hello</button>
</div>
It would probably be better (and more scallable) to attribute a class selector to this element as well so that you can manage and maintain these styles externally with other declared styles (in style.css), e.g:
Amended html structure:
<div class="foobar" data-reactid=".0">
<input class="input" data-reactid=".0.0">
<div class="dropdown" style="position:absolute;z-index:9999;" data-reactid=".0.1"></div>
<button data-reactid=".0.2">Hello</button>
</div>
Additional css declarations:
.foobar {
width:200px;
position: relative; /* required for nested absolute element */
}
Reference:
position - CSS | MDN
z-index - CSS | MDN
Related
I have this span element. am fetching data and putting some text in that span element, therefore sometimes that span elements width is 200 px, sometimes its 100px. I want this span to have margin-right: half of its width. I am using this technique:
const [width, setWidth] = useState()
const ref = useRef(null)
useLayoutEffect(()=>{
setWidth(ref.current.offsetWidth);
},[])
<span className='big ' id='meria' ref={ref} style={{marginRight: width / 2 }}>sometext</span>
I want the width element to re-render on change of window.location.pathname, but I cant use that as dependency.
any tips?
Don't use a dependency array. The useEffect would be called on each render, but if the offsetWidth didn't change, setting the state won't have any effect:
useLayoutEffect(() => {
setWidth(ref.current.offsetWidth);
})
Since the change happens on each render, you can skip the state, and just calculate it directly from the ref as suggested by kind user's comment:
<span className='big ' id='meria' ref={ref} style={{marginRight: ref.current.offsetWidth / 2 }}>sometext</span>
Note: margin doesn't work on inline elements, you should change the display CSS property inline-block or block.
Another option for this specific case is to set the margin using percentage in CSS, since according to MDN:
The size of the margin as a percentage, relative to the inline size
(width in a horizontal language, defined by writing-mode) of the
containing block.
Do you use a seperate CSS-stylesheet? (even if you don't this still should work, because it's basically just CSS) - If yes you can easily do some CSS trickery to get the same effect without a single line of JS needed You would do that as follows:
Wrap the span (or whatever element you want to have the dynamic margin for) in a div - this div then gets the width: fit-content - you can now set margin-right: 50% on your span element. This is everything you need for your desired result (The 50% always are calculated from the parent ... and with fit-content the parent will be the childs width)
Since you are using spans you'll need to add white-space: nowrap to the span (otherwise the span wouldn't overflow out of the div and just linebreak, which is not what we want)
span {
margin-left: 50%;
width: fit-content;
white-space: nowrap;
border: solid
}
.container {
width: fit-content;
}
<div class="container">
<span>short one</span>
</div>
<div class="container">
<span>this one is longer</span>
</div>
<div class="container">
<span>and this one is even longer than the one before</span>
</div>
I used margin-left for demonstartion purpouse (just change it tho whatever you need . the snippet is more to show what I meant, and show the dinamically changing maring based on width of the span)
JSFiddle demo
In a dropdown with a container element set to display: inline-block, there is a label (always visible, toggles dropdown overlay), and the overlay element itself. I am setting the overlay container to height: 0 and wishing to allow the overlay contents to exceed the height of the container, without affecting any parent elements. However, I am seeing some strange results - the overlay container is causing the parent of the dropdown to fully enclose the overlay contents too!
In the following HTML, ib = the inline block and h0 = the height:0 overlay container. See the jsfiddle demo to see it in action.
<div>
Sort by this
<span id="ib">
<span>LABEL</span>
<div id="h0">
DROPDOWN<br />
</div>
</span>
</div>
I don't wish to use position: absolute on the overlay, as I would like the contents of the overlay to drive the final width of the label. Surprisingly, I can achieve the desired outcome with the following css:
#ib { display: inline-flex; flex-direction: column; }
I'm happy to use that workaround for now, but also interested in the "why" behind this bizarre effect.
Your issue is about vertical-align rule for inline block elements. By default it baseline, here is some spec:
Baseline: Align the baseline of the box with the baseline of the parent box.
See also:
The height of each inline-level box in the line box is calculated. For replaced elements, inline-block elements, and inline-table elements, this is the height of their margin box; for inline boxes, this is their 'line-height'.
source: https://www.w3.org/TR/CSS2/visudet.html#line-height
and
CSS assumes that every font has font metrics that specify a characteristic height above the baseline and a depth below it. In this section we use A to mean that height (for a given font at a given size) and D the depth. We also define AD = A + D, the distance from the top to the bottom.
source: https://www.w3.org/TR/CSS2/visudet.html#inline-box-height
So your fix is ignore default inline-level block height setted via line-height and font-size by setting vertical-align: top/bottom/middle/text-top/text-bottom by your choice.
And fixed code:
.dropdown {
display: inline-block;
vertical-align: top;
}
.overlay {
height: 0;
}
<div>
sort by this
<span class="dropdown">
<span>LABEL</span>
<div class="overlay">
DROPDOWN<br />
DROPDOWN<br />
DROPDOWN
</div>
</span>
</div>
<hr />
<a> is positioned relatively, while <span> is nested inside it and is positioned absolutely. Yet, <span> appears below the bottom left corner of the image, instead of appearing at top left corner of it's relative parent. I can't understand why does it not ignore it's sibling's position.
Here is my code:
.pos-rel {
position:relative;
}
.pos-abs {
position:absolute;
top:0px;
right:0px;
}
<a href="#" class="pos-rel">
<img src="http://placehold.it/270x270" class="img-responsive">
<span class="label label-primary pos-abs">Overlay</span>
</a>
Picture of an expected behavior:
It is most likely that I don't understand how position:relative and position:absolute work together in this case. Can anyone explain why the behavior illustrated on the picture is not taking place?
This is due to how the display of <a> is expressed in terms of CSS. Usually, it's display: inline. Absolute positioning works quite differently when the position: relative ancestor is an inline element:
The containing block of an element is defined as follows:
If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed', in the following way:
In the case that the ancestor is an inline element, the containing block is the bounding box around the padding boxes of the first and the last inline boxes generated for that element. In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined.
The inline box generated by the <a> (to contain the <img>) is the same height as the line box that it's in, and the height of the <img>, which itself is inline, is irrelevant. So the absposed element is placed at around the same height as the line box the <img> is sitting on. The <img> is positioned the way it is because it's sitting on the baseline of the <a>.
As you can imagine, setting the <a> to display: block produces the expected behavior.
<a> is inline, you must change it to block and set width or inline-block to let it assume the width of it's contents.
Included the jsfiddle to reflect what you were aiming for: https://jsfiddle.net/gq30uct4/
NEW ANSWER:
As #ISuthan Bala wrote, <a> is inline element, so you have to add your relative class to an additional DIV inside the <a> tag:
http://codepen.io/anon/pen/VaqmWN
.pos-rel {
position:relative;
}
.pos-abs {
display: block;
position:absolute;
top:0px;
left:0px;
}
<a href="#">
<div class="pos-rel">
<img src="http://placehold.it/270x270" >
<span class="label label-primary pos-abs">Overlay</span>
</div>
</a>
I have a site with multiple menus. I have defined enteire page content inside a div with a class container and applied bootstrap css styles (margin-left:auto and margin-right:auto etc.,) to make the page centered. Now elements in one of the file must start from extreme left side of the browser. Because of the css applied for parent i could not start the element from left side. I have applied margin-left with minus pixels to solve the issue. But when the browser window is small element is not completely visible in browser because of minus value applied for margin-left.
<div class="container" style="margin-left:auto;margin-right:auto">
<div id="start_from_left" style="margin-left=-50px"> </div>
</div>
if your tags have
style=""
that takes priority over anything else.
What you could try is the !important on your css tags, but its bad practice.
eg
margin-left:1px!important;
margin-right:0px!important;
You can have additional class for your container.
.container {
margin-left: auto;
margin-right: auto;
}
.container.wide {
margin-left: 0;
margin-right: 0;
}
Here, In this example I have two divisions where one is nested within another. I'm calling the outer division a parent and the division which is nested a child.
CSS
/* for parent div tag*/
#parent{
width: 500px;
height: 300px;
z-index: 1;
background-color: #CCC;
border:thin solid black;
margin:25px;
padding:20px;
}
/* for child div tag */
#child{
border:thin solid #F00;
height:50px;
background-color:#FFC;
}
HTML
<!-- Start of parent tag -->
<div id="parent">
<p> This is parent div tag. </p>
<!-- Child div tag -->
<div id="child">
<p> This is child div tag. </p>
</div>
<!-- End of parent tag. -->
</div>
It looks like this in the web browser:
My question is: How does the child div tag gets the size of it's width? Is it because of inheritance from the parent div tag or is it just by default behavior that it will expand up to the parent div container if you don't specify a width?
Width cannot be inherited. What you are seeing is default behavior.
See the specs on block level elements
"Each block-level element generates a principal block-level box that contains descendant boxes and generated content and is also the box involved in any positioning scheme." -- W3C
By default, block elements have a width of 100%. That means that if a width isn't specified, it will be 100% of the parent.
In this case, the parent's width is 542px.
The calculation is based on width:500px; + padding-left:20px;+ padding-right:20px; +border 2px;
The child's width is exactly 500px. (100% of the parents width - minus padding/border).
jsFiddle here You can play around with it and inspect the elements.
Yes, block level elements will expand to the width that the parent will let them (width minus padding).
Edit: if you want the child to only be as width as it contents, add display: inline-block to it (also check here).
Yes, the div is by default a block element. And because you didn't specify width its value is "auto". This means that the browser calculates the width and normally that's 100% of the width of the parent element.