Why is scrollWidth not including a child element's right margin? - javascript

I'm trying to have a <div> slide open to the minimum size required to contain its content without anything overflowing.
Here's what I've got so far:
window.onload = function() {
setTimeout(() => {
var thing = document.getElementById('thing');
thing.style.maxWidth = thing.scrollWidth + "px";
thing.style.maxHeight = thing.scrollHeight + "px";
}, 1000);
}
body {
background-color: black;
}
p {
margin: 40px;
padding: 0;
color: yellow;
background-color: green;
font-family: monospace;
}
div.expander {
margin: 0;
padding: 0;
max-width: 0;
max-height: 0;
overflow: hidden;
transition:
max-width 1s,
max-height 1s;
border: 1px solid red;
}
hr {
margin: 0;
padding: 0;
border-top: 1px solid blue;
border-right: none;
border-bottom: none;
border-left: none;
}
<div id="thing" class="expander">
<p>Hello, world!</p>
<hr>
<p>Goodbye, world!</p>
</div>
See how neither <p> is wide enough to contain its text without overflowing? That's what I'm trying to prevent. Clearly, scrollHeight is doing what I expect. Why isn't scrollWidth?

This isn't the full picture, but margin-right (or margin-left) gets ignored in specific scenarios. This is intended block functionality in CSS. From the specs:
'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
If all of the above have a computed value other than 'auto', the values are said to be "over-constrained" and one of the used values will have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl', this happens to 'margin-left' instead.
In my opinion, this isn't very intuitive behavior and I prefer to avoid margins for block layout where possible (for more reasons than just this). So, you could add a wrapper <div> around your <p> tags and use padding instead. This also enables you to use a border between items instead of adding <hr> to your content. I think the proper semantics could go either way, depending on your real-world usage of this. To quote the MDN docs:
The HTML <hr> element represents a thematic break between paragraph-level elements: for example, a change of scene in a story, or a shift of topic within a section.
Historically, this has been presented as a horizontal rule or line. While it may still be displayed as a horizontal rule in visual browsers, this element is now defined in semantic terms, rather than presentational terms, so if you wish to draw a horizontal line, you should do so using appropriate CSS.
But, while that fixes a few concerns, that doesn't fix everything. The calculated width of the <p> elements is still less than min-content. So, we can force this with min-width: min-content;, or by using a different display value (probably on expander). This changes which algorithms are used for calculating widths under the hood.
Last note before the full example code: max-width and max-height are a decent trick but are really only useful for this kind of thing if you're trying to avoid modifying styles from JS (think :hover or adding and removing an .open class from JS, instead of setting width and height, directly)
setTimeout(() => {
const expander = document.querySelector('.js-expander');
expander.style.width = expander.scrollWidth + "px";
expander.style.height = expander.scrollHeight + "px";
}, 1000);
body {
background-color: black;
}
.expander {
display: grid;
width: 0px;
height: 0px;
overflow: hidden;
border: 1px solid red;
transition:
width 1s,
height 1s;
}
.expander-item {
padding: 40px;
font-family: monospace;
border-bottom: 1px solid blue;
}
.expander-item:last-child { border-bottom: 0px; }
.expander-item > * {
color: yellow;
background-color: green;
}
.expander-item > :first-child { margin-top: 0; }
.expander-item > :last-child { margin-bottom: 0; }
<div class="expander js-expander">
<div class="expander-item">
<p>Hello, world!</p>
</div>
<div class="expander-item">
<p>Goodbye, world!</p>
</div>
</div>
UPDATE: Based on comments, I thought I might include how <hr/> might be added back in:
setTimeout(() => {
const expander = document.querySelector('.js-expander');
expander.style.width = expander.scrollWidth + "px";
expander.style.height = expander.scrollHeight + "px";
}, 1000);
body {
background-color: black;
}
.expander {
display: grid;
width: 0px;
height: 0px;
overflow: hidden;
border: 1px solid red;
transition:
width 1s,
height 1s;
}
.expander-group {
padding: 40px;
font-family: monospace;
}
.expander-group > * {
color: yellow;
background-color: green;
}
.expander-group > :first-child { margin-top: 0; }
.expander-group > :last-child { margin-bottom: 0; }
.expander > hr {
margin: 0;
padding: 0;
border-top: 1px solid blue;
border-right: none;
border-bottom: none;
border-left: none;
}
<div class="expander js-expander">
<div class="expander-group">
<p>Hello, world 1!</p>
<p>Hello, world 2!</p>
</div>
<hr>
<div class="expander-group">
<p>Goodbye, world!</p>
</div>
</div>

Related

Overflow scrolling on dynamically moving elements

var block = document.getElementById('block')
function myFunct() {
block.style.transform = 'translateX(-400px)'
}
.container {
position:relative;
width:400px;
height:150px;
margin:auto;
background-color: blue;
overflow:scroll;
}
#block {
position:absolute;
height:25px;
width:100%;
left:50%;
bottom:50%;
overflow: scroll;
background-color: yellow;
border:1px solid black;
align-self: flex-end;
}
<div class="container">
<div id="block"></div>
<button onclick='myFunct()'>CLICK</button>
</div>
In my example the block overflows the right side of the container and overflow is set to scroll. So you can scroll the right side and see the rest of the block. Then when I run the function, the block moves so it's overflowing the left side of the container. However it does not adjust to allow for scrolling left to see the rest of the block. What is the solution to allow for scrolling of other sides after functions are ran and blocks are moved to overflow those different sides.
Based on this, a the solution can be sort of 'hack' like this:
var block = document.getElementById('block')
function myFunct() {
document.getElementsByClassName('container')[0].dir = 'rtl';
block.style.transform = 'translateX(-400px)'
}
.container {
position:relative;
width:400px;
height:150px;
margin:auto;
background-color: blue;
overflow:scroll;
}
#block {
position:absolute;
height:25px;
width:100%;
left:50%;
bottom:50%;
overflow: scroll;
background-color: yellow;
border:1px solid black;
align-self: flex-end;
}
<div class="container">
<div id="block"></div>
<button onclick='myFunct()'>CLICK</button>
</div>
Thanks to #TemaniAfif that point me.
The real problem at hand is that the css property transform will only trigger a repaint on the Composite Layer, this was an optimization decision made to facilitate animations without triggering repaints on the Layout Layer . To trigger an entire layout repaint you should use a layout property like left or right:
Example:
function myFunct() {
block.style.left = '0px'
}
Also the reason you are getting the scrollbar on initial load is because you have:
#block {
...
left: 50%
...
}
More here on Compositor-Only Properties
Edit:
Although the above is true, switching to 'style.left' will still not cut it
because block level elements have a default content flow direction of left to right or in css direction: ltr so this means you'll need to modify the content direction as well which should cancel out the need to use style.left. See below:
var block = document.getElementById('block')
var container = document.querySelector('.container')
function myFunct() {
block.style.transform = 'translateX(-400px)'
container.style.direction = 'rtl'
}
.container {
position:relative;
width:400px;
height:150px;
margin:auto;
background-color: blue;
overflow:scroll;
}
#block {
position:absolute;
height:25px;
width:100%;
left:50%;
bottom:50%;
overflow: scroll;
background-color: yellow;
border:1px solid black;
}
<div class="container">
<div id="block"></div>
<button onclick='myFunct()'>CLICK</button>
</div>
Is it important where the yellow block shifts and gets positioned? if you just set the #block width to allow for the extra 400px with width: calc(100% + 400px) then you can see it with overflow after calling the function.
var block = document.getElementById('block')
function myFunct() {
block.style.transform = 'translateX(-400px)'
}
.container {
position: relative;
width: 400px;
height: 150px;
margin: auto;
background-color: blue;
overflow: scroll;
}
#block {
position: absolute;
height: 25px;
width: calc(100% + 400px);
left: 50%;
bottom: 50%;
overflow: scroll;
background-color: yellow;
border: 1px solid black;
align-self: flex-end;
}
<div class="container">
<div id="block"></div>
<button onclick='myFunct()'>CLICK</button>
</div>
This is what I came up with. My solution applies to my use which is to have a have a line of text run across the page right to left and being able to scroll left to see the parts of the sentence that eventually overflow. The #fill div lets the entire wrapper be scrollable rather than just the span. There's a way to do it with two divs using "line-height = (size of container)" but I figured that would lead to future problems so I avoided. The key to my way was the "outer.scrollLeft += outer.scrollWidth" within the function. There's a more fluid way of doing this as my way is choppy by moving the entire span element left which is what my original question was pointed towards but ultimately not how I did it. Meshu's solution also worked in application to the question I asked. "Direction:rtl" also allows for a solution.
var inset = document.getElementById('inset')
var fill = document.getElementById('fill')
var text = 'There was a lamb who had a cow and a farmer was involved and then you make a moo sound and yell BINGO and that is how the song goes.'; /*Obviously you can change this jibberish */
var words = text.split(" ") /* breaking the text into an array of each word */
var i = 0
var timer = 5; /*How long the text will take to run through in seconds*/
var wordTime = (timer / words.length) * 1000; /* Time before the next word takes to the screen. As of this code, all words have equal time */
var myVar = setInterval(myFunct, wordTime);
function myFunct() {
if (i == words.length) {
clearInterval(myVar); /* Stops running when all words are passed through */
} else {
inset.innerHTML += " " + words[i];
}
i++
let outer = fill
outer.scrollLeft += outer.scrollWidth; /*important */
}
#wrapper {
position: absolute;
display: flex;
height: 100px;
width: 100%;
min-width: 100%;
max-width: 100%;
left: 0;
right: 0;
bottom: 0;
align-items: center;
text-align: right;
color: whitesmoke;
font-family: sans-serif;
font-size: 200%;
background-color: rgba(0, 0, 0, 0.7);
}
#fill {
display: flex;
width: 100%; /*You can change this to change how much of container the text overtakes */
height: 100%;
max-width: 100%;
align-items: center;
overflow: auto;
}
#inset {
padding-left:5px;
white-space: nowrap;
margin-left:auto;
}
<div id='wrapper'>
<div id='fill'>
<span id='inset'></span>
</div>
</div>
.

Adding non-centered text shifts centered elements [duplicate]

This question already has answers here:
How to prevent scrollbar from repositioning web page?
(26 answers)
Closed 4 years ago.
After putting a centered header, I add a non-centered output with JS. After the output is produced, the header shifts a bit left. What can be done to tackle this problem?
let output = [];
function spit() {
for (let i = 0; i < 100; i++) {
output.push(i);
}
document.getElementById("output").innerHTML =
output.join("<br>");
}
.header {
background-color: lightgray;
border: none;
color: black;
padding: 17px 25px;
display: block;
text-align: center;
font-size: 36px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
<h2 id="dictName" class="header">
Testing Page
</h2>
<button style="font-size:20pt;height:35pt" onclick="spit()">
Press me!
</button>
<p id="output">
</p>
One crazy solution might be to set you body height to the view port height, that way you start off with a scroll, avoiding the shift when the button gets pressed.
let output = [];
function spit() {
for (let i = 0; i < 100; i++) {
output.push(i);
}
document.getElementById("output").innerHTML =
output.join("<br>");
}
body {
min-height: 100vh;
}
.header {
background-color: lightgray;
border: none;
color: black;
padding: 17px 25px;
display: block;
text-align: center;
font-size: 36px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
<h2 id="dictName" class="header">
Testing Page
</h2>
<button style="font-size:20pt;height:35pt" onclick="spit()">
Press me!
</button>
<p id="output">
</p>
I added a universal { margin:0; padding:0;} to your css code. The code did seem to be centered but I think the margin of -50 (that's being created by the auto margin ) is throwing off the look.
let output = [];
function spit() {
for (let i = 0; i < 100; i++) {
output.push(i);
}
document.getElementById("output").innerHTML =
output.join("<br>");
}
* {
margin: 0px; padding:0px;
}
button {
/*margin-left:15px;*/
margin-top:7px;
font-size: 20pt;
height: 35pt;
}
.header {
background-color: lightgray;
/* border: 15px solid white;*/ /*use the commented props if you still want the "indented" effect" */
color: black;
padding: 17px 25px;
display: block;
text-align: center;
font-size: 36px;
width: 100%;
margin-right:auto;
margin-left:auto;
}
<h2 id="dictName" class="header">
Testing Page
</h2>
<button onclick="spit()">
Press me!
</button>
<p id="output">
</p>
IF you don't want the h1 to shift due to the scrollbar, you would have to calculate, using css calc() (and maybe some other things too), 50vw - (widthOfH1/2). This works because the vw unit (viewport width) is not affected by the scrollbar.
One way for the scrollbar to not affect the centering of the h1 would be to use JQuery.
$(#dictName).style.marginLeft = 'calc(50vw -'+(this.width/2)+'px)';
I haven't tested this so I'm not 100% sure if it will work, but please tell me if it does or doesn't. You may need to rerun this code when the button is pressed.
After some googling it seems I found the easiest way to solve this problem here, on SO:
How to prevent scrollbar from repositioning web page?
html {
overflow-y: scroll;
}
Probably, the question should be closed as duplicate, but I don't have enough reputation to do it.

How can i get 2 classes by id inside the same function

I need to target two div elements and toggle their classes simultanouesly.
I understand that I can get multiple divs "by ID" by using .querySelectorAll
but when I get to .classlist.toggle ("NewClassName"); how can I target two classes??
So here's some code:
#small-div{
background-color:#aaaaaa;
border: 3px solid #aaaaaa;
padding: 0px 0px 0px 0px;
margin: auto 10px auto auto;
border-radius: 10px;
overflow: auto;
}
.tobetoggled{
width: 45%;
float: left;
}
#small-div2{
background-color:#aaaaaa;
border: 3px solid #aaaaaa;
padding: 0px 0px 0px 0px;
margin: auto 10px auto auto;
border-radius: 10px;
overflow: auto;
}
.tobetoggled2{
width: 45%;
float: right;
}
.toggletothis{
width: 100%;
float: left;
position: fixed;
display: block;
z-index: 100;
}
.toggletothis2{
width: 100%;
float: left;
position: fixed;
display: block;
z-index: 100;
}
.whensmalldivistoggled{
display: none;
}/* when small-div is clicked, small-div toggles to class "tobetoggled" while small-div 2 simultaneously toggles to class "whensmalldivistoggled" (the display none class) */
<div id="container">
<div class="tobetoggled" onclick="function()" id="small-div">
</div>
<div class="tobetoggled2" onclick="separatefunction()" id="small-div2">
</div>
</div> <!-- end container -->
<script>
function picClicktwo() {
document.querySelectorAll("small-div, small-div2").classList.toggle("toggletothis, whensmalldivistoggled");
}
</script>
So as you can see one div is on the right, the other is on the left, each set to 45% width. So if I toggle one div to 100% width the browser still respects the other divs space instead of taking the whole 100%.
So I'm thinking if I can get the div on the right ,for example, to not display when the div on the left is toggled, it will be out of the way so the left div can take all 100%
Maybe im going about this the wrong way. Any help is welcome. Thanks.
You can create a single javascript function that sets appropriate classes on each element. Since you have only two elements it is not too complex.
HTML
<div id="container">
<div id="lefty" onclick="toggle('lefty', 'righty')">Lefty</div>
<div id="righty" onclick="toggle('righty', 'lefty')">Righty</div>
</div>
JS
function toggle(target, other)
{
var t = document.getElementById(target);
var o = document.getElementById(other);
if (!t.className || t.className == "inative")
{
t.className = "active";
o.className = "inactive";
}
else
{
t.className = "";
o.className = "";
}
}
CSS
#container {
background-color: lightgreen;
padding: 15px 0;
}
#container div {
color: white;
width: 45%;
display: inline-block;
}
#lefty {
background-color: blue;
}
#righty {
background-color: purple;
}
#container div.active {
width: 90%;
}
#container div.inactive {
display:none;
}
https://jsfiddle.net/dLbu9odf/1/
This could be made more elegant or capable of handling more elements with something like toggle(this) and then some DOM traversal and iteration in javascript, but that's a bit beyond scope. If that were the case I would recommend jQuery.

Show one element if another is above a certain height

I have a following HTML:
<span class="day-number">{{day-number}}</span>
<div class="event-box">
<div class="event-container">
</div>
<div class="more-events">more ...</div>
</div>
Event-container is filled with an unknown number of .event elements like the following:
<div class="event">{{event-name}}</div>
I want to show or hide the .more element based on if the .event-container has a height of over 76px (equal to the height of four .event elements stacked).
The styling for the above elements:
.event {
text-align: left;
font-size: .85em;
line-height: 1.3;
border-radius: 3px;
border: 1px solid #3a87ad;
background-color: #3a87ad;
font-weight: normal;
color: whitesmoke;
padding: 0 1px;
overflow: hidden;
margin-bottom: 2px;
cursor: pointer;
}
.event-box {
max-height: 76px;
overflow: hidden;
position:relative;
}
.event-box .more-events {
height: 10px;
position: absolute;
right: 0;
bottom: 10px;
display: none;
z-index: 5;
}
No styling for .event-container
I can do what I want with Javascript (jQuery):
$(".event-box").each(function(){
var $this = $(this);
if($this.children(".event-container").height() > 76){
$this.children(".more-events").css("display", "block");
} else {
$this.children(".more-events").css("display", "");
}
});
And run that every time a make a change, but I'd rather do it with CSS.
Is this possible? Maybe with pseudo elements or media queries or something?
JSFIDDLE: http://jsfiddle.net/pitaj/LjLxuhx2/
If changing the markup is acceptable there is a possibility to achieve a somewhat similarly looking page without using JavaScript to show or hide, here is the Fiddle
I have removed <div class="more-events">more ...</div> line and made elements of event class to get hide when it is necessary I also made them to appear when hovering over more ... .
The CSS I have added:
.event:nth-child(n){
display: none;
}
.event:nth-child(1),.event:nth-child(2),.event:nth-child(3),.event:nth-child(4){
display: block;
}
.event:nth-child(5){
text-indent: -9999px;
position: relative;
display: block;
color: black;
border: none;
background-color: #FFF;
}
.event:nth-child(5)::before{
position: absolute;
text-indent: 0px;
content: "more ...";
display: block;
}
.event:nth-child(5):hover{
position: static;
text-indent: 0;
border: 1px solid #3a87ad;
background-color: #3a87ad;
color: whitesmoke;
}
.event:nth-child(5):hover::before{
display:none;
}
.event:nth-child(5):hover ~ .event:nth-child(n){
display: block;
}
And for .event-box class I have commented out max-height: 76px; because in my browser 76px was not equal to the height of four .event elements stacked. Also removed update function.
I dont think it's possible using css only. but for better approach in what you are trying to do.instead of using max-height for .event-box I use this css which is add display:none to +4.event on your event container:
.event-box .event-container .event:nth-child(n+5){
display: none;
}
and now when it's more than 4 .event your more text appears. FIDDLE
UPDATE:
HERE I make little change in you js as well and make it more professional,
while you are using template to render the page, maybe you can do it as follow
<div class="event-container">
{{#each events}}
<div class="event">{{event-name}}</div>
{{/each}}
</div>
{{#if canshowmore}}
<div class="more-events">more ...</div>
{{/if}}
and
function canshowmore() {
return events.length >= 4;
}

Adding a rounded similar to border-radius to outline [duplicate]

Is there any way of getting rounded corners on the outline of a div element, similar to border-radius?
I had an input field with rounded border and wanted to change colour of focus outline. I couldn't tame the horrid square outline to the input control.
So instead, I used box-shadow. I actually preferred the smooth look of the shadow, but the shadow can be hardened to simulate a rounded outline:
input, input:focus {
border: none;
border-radius: 2pt;
box-shadow: 0 0 0 1pt grey;
outline: none;
transition: .1s;
}
/* Smooth outline with box-shadow: */
.text1:focus {
box-shadow: 0 0 3pt 2pt cornflowerblue;
}
/* Hard "outline" with box-shadow: */
.text2:focus {
box-shadow: 0 0 0 2pt red;
}
<input class="text1">
<br>
<br>
<input type=text class="text2">
I usually accomplish this using the :after pseudo-element:
of course it depends on usage, this method allows control over individual borders, rather than using the hard shadow method.
you could also set -1px offsets and use a background linear gradient (no border) for a different effect once again.
body {
margin: 20px;
}
a {
background: #999;
padding: 10px 20px;
border-radius: 5px;
text-decoration: none;
color: #fff;
position: relative;
border: 2px solid #000;
}
a:after {
content: '';
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: 5px;
border: 2px solid #ccc;
}
Button
Similar to Lea Hayes above, but here's how I did it:
div {
background: #999;
height: 100px;
width: 200px;
border: #999 solid 1px;
border-radius: 10px;
margin: 15px;
box-shadow: 0px 0px 0px 1px #fff inset;
}
<div></div>
No nesting of DIVs or jQuery necessary, Altho for brevity I have left out the -moz and -webkit variants of some of the CSS. You can see the result above
Use this one:
box-shadow: 0px 0px 0px 1px red;
I wanted some nice focus accessibility for dropdown menus in a Bootstrap navbar, and was pretty happy with this:
a.dropdown-toggle:focus {
display: inline-block;
box-shadow: 0 0 0 2px #88b8ff;
border-radius: 2px;
}
Visit Stackoverflow
We may see our wishes soonish by setting outline-style: auto It's on WebKits radar: http://trac.webkit.org/changeset/198062/webkit
See ya in 2030.
You're looking for something like this, I think.
div {
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
border: 1px solid black;
background-color: #CCC;
height: 100px;
width: 160px;
}
Edit
There is a Firefox-only -moz-outline-radius properly, but that won't work on IE/Chrome/Safari/Opera/etc. So, it looks like the most cross-browser-compatible way* to get a curved line around a border is to use a wrapper div:
div.inner {
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
border: 1px solid black;
background-color: #CCC;
height: 100px;
width: 160px;
}
div.outer {
display: inline-block;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
border: 1px solid red;
}
<div class="outer">
<div class="inner"></div>
</div>
*aside from using images
Firefox 88+: border-radius
From April 2021 you will be able to use a simple CSS for Firefox:
.actual {
outline: solid red;
border-radius: 10px;
}
.expected {
border: solid red;
border-radius: 10px;
}
In Firefox 88+,
<span class="actual">this outline</span>
should look like
<span class="expected">this border</span>
Current behaviour in Firefox 86.0:
Webkit: no solution
Using outline-style: auto will tell the «user agent to render a custom outline style»: see [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/outline-style(.
Webkit-based browsers will then draw the outline over the border, when you use outline-style: auto. It's difficult to style it properly.
.actual {
outline: auto red;
border-radius: 10px;
}
.expected {
border: solid red;
border-radius: 10px;
}
In WebKit browsers (Chrome, Edge),
<span class="actual">this outline</span>
should look close to
<span class="expected">this border</span>
Current behaviour in Chrome 89.0:
More information
From Firefox 88 (to be released April 20 2021), outline will follow the shape of border-radius.
The current -moz-outline-radius will become redundant and will be removed.
See MDN's entry about -moz-outline-radius:
From Firefox 88 onwards, the standard outline property will follow the shape of border-radius, making -moz-outline-radius properties redundant. As such, this property will be removed.
(Feb 2023)
As far as I know, the Outline radius is only supported by Firefox and Firefox for android.
-moz-outline-radius: 1em;
I just found a great solution for this, and after looking at all the responses so far, I haven't seen it posted yet. So, here's what I did:
I created a CSS Rule for the class and used a pseudo-class of :focus for that rule. I set outline: none to get rid of that default light-blue non-border-radius-able 'outline' that Chrome uses by default. Then, in that same :focus pseudo-class, where that outline no longer exists, I added my radius and border properties. Leading to the following
outline: none;
border-radius: 5px;
border: 2px solid maroon;
to have a maroon-colored outline with a border radius that now appears when the element is tab-selected by the user.
If you want to get an embossed look you could do something like the following:
.embossed {
background: #e5e5e5;
height: 100px;
width: 200px;
border: #FFFFFF solid 1px;
outline: #d0d0d0 solid 1px;
margin: 15px;
}
.border-radius {
border-radius: 20px 20px 20px 20px;
-webkit-border-radius: 20px;
-moz-border-radius: 20px;
-khtml-border-radius: 20px;
}
.outline-radius {
-moz-outline-radius: 21px;
}
<div class="embossed"></div>
<div class="embossed border-radius"></div>
<div class="embossed border-radius outline-radius">-MOZ ONLY</div>
I have not found a work around to have this work in other browsers.
EDIT: The only other way you can do this is to use box-shadow, but then this wont work if you already have a box shadow on that element.
Chrome 94.0+
I tested it in chrome 94.0 and it seems that the outline property honors the border-radius now.
.outline {
outline: 2px solid red;
}
.border {
border: 2px solid red;
}
.outline-10 {
border-radius: 10px;
}
.border-2 {
border-radius: 2px;
}
.outline-2 {
border-radius: 2px;
}
.border-10 {
border-radius: 10px;
}
.outline-50 {
border-radius: 50%;
}
.border-50 {
border-radius: 50%;
}
.circle {
display: inline-block;
width:50px;
height: 50px;
}
<strong>Test this in chrome 94.0+</strong>
<br/><br/>
border-radius: 2px
<span class="outline outline-2">outline</span>
<span class="border border-2">border</span>
<br/><br/>
border-radius: 10px
<span class="outline outline-10">outline</span>
<span class="border border-10">border</span>
<br/><br/>
border-radius: 50%
<span class="outline outline-50">outline</span>
<span class="border border-50">border</span>
<span class="outline circle outline-50">outline</span>
<span class="border circle border-50">border</span>
As others have said, only firefox supports this. Here is a work around that does the same thing, and even works with dashed outlines.
.has-outline {
display: inline-block;
background: #51ab9f;
border-radius: 10px;
padding: 5px;
position: relative;
}
.has-outline:after {
border-radius: 10px;
padding: 5px;
border: 2px dashed #9dd5cf;
position: absolute;
content: '';
top: -2px;
left: -2px;
bottom: -2px;
right: -2px;
}
<div class="has-outline">
I can haz outline
</div>
No. Borders sit on the outside of the element and on the inside of the box-model margin area. Outlines sit on the inside of the element and the box-model padding area ignores it. It isn't intended for aesthetics. It's just to show the designer the outlines of the elements. In the early stages of developing an html document for example, a developer might need to quickly discern if they have put all of the skeletal divs in the correct place. Later on they may need to check if various buttons and forms are the correct number of pixels apart from each other.
Borders are aesthetic in nature. Unlike outlines they are actually apart of the box-model, which means they do not overlap text set to margin: 0; and each side of the border can be styled individually.
If you're trying to apply a corner radius to outline I assume you are using it the way most people use border. So if you don't mind me asking, what property of outline makes it desirable over border?
COMBINING BOX SHADOW AND OUTLINE.
A slight twist on Lea Hayes answer
I found
input[type=text]:focus {
box-shadow: 0 0 0 1pt red;
outline-width: 1px;
outline-color: red;
}
gets a really nice clean finish. No jumping in size which you get when using border-radius
There is the solution if you need only outline without border. It's not mine. I got if from Bootstrap css file. If you specify outline: 1px auto certain_color, you'll get thin outer line around div of certain color. In this case the specified width has no matter, even if you specify 10 px width, anyway it will be thin line. The key word in mentioned rule is "auto".
If you need outline with rounded corners and certain width, you may add css rule on border with needed width and same color. It makes outline thicker.
I was making custom radio buttons and the best customisable way i've found is using pseudo elements like this: Codepen
/*CSS is compiled from SCSS*/
.product-colors {
margin-bottom: 1em;
display: flex;
align-items: center;
}
.product-colors label {
position: relative;
width: 2.1em;
height: 2.1em;
margin-right: 0.8em;
cursor: pointer;
}
.product-colors label:before {
opacity: 0;
width: inherit;
height: inherit;
padding: 2px;
border: 2px solid red;
border-radius: 0.2em;
content: "";
position: absolute;
z-index: 1;
background: transparent;
top: -4px;
left: -4px;
}
.product-colors input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.product-colors input:checked + label:before, .product-colors input:focus + label:before {
opacity: 1;
}
<div class="product-colors">
<input type="radio" name="cs" id="cs1" value="black">
<label for="cs1" style="background:black"></label>
<input type="radio" name="cs" id="cs2" value="green">
<label for="cs2" style="background:green"></label>
<input type="radio" name="cs" id="cs3" value="blue">
<label for="cs3" style="background:blue"></label>
<input type="radio" name="cs" id="cs4" value="yellow">
<label for="cs4" style="background:yellow"></label>
</div>
clip-path: circle(100px at center);
This will actually make clickable only circle, while border-radius still makes a square, but looks as circle.
The simple answer to the basic question is no. The only cross-browser option is to create a hack that accomplishes what you want. This approach does carry with it certain potential issues when it comes to styling pre-existing content, but it provides for more customization of the outline (offset, width, line style) than many of the other solutions.
On a basic level, consider the following static example (run the snippent for demo):
.outline {
border: 2px dotted transparent;
border-radius: 5px;
display: inline-block;
padding: 2px;
margin: -4px;
}
/* :focus-within does not work in Edge or IE */
.outline:focus-within, .outline.edge {
border-color: blue;
}
br {
margin-bottom: 0.75rem;
}
<h3>Javascript-Free Demo</h3>
<div class="outline edge"><input type="text" placeholder="I always have an outline"/></div><br><div class="outline"><input type="text" placeholder="I have an outline when focused"/></div> *<i>Doesn't work in Edge or IE</i><br><input type="text" placeholder="I have never have an outline" />
<p>Note that the outline does not increase the spacing between the outlined input and other elements around it. The margin (-4px) compensates for the space that the outlines padding (-2px) and width (2px) take up, a total of 4px.</p>
Now, on a more advanced level, it would be possible to use JavaScript to bootstrap elements of a given type or class so that they are wrapped inside a div that simulates an outline on page load. Furthermore, event bindings could be established to show or hide the outline on user interactions like this (run the snippet below or open in JSFiddle):
h3 {
margin: 0;
}
div {
box-sizing: border-box;
}
.flex {
display: flex;
}
.clickable {
cursor: pointer;
}
.box {
background: red;
border: 1px solid black;
border-radius: 10px;
height: 5rem;
display: flex;
align-items: center;
text-align: center;
color: white;
font-weight: bold;
padding: 0.5rem;
margin: 1rem;
}
<h3>Javascript-Enabled Demo</h3>
<div class="flex">
<div class="box outline-me">I'm outlined because I contain<br>the "outline-me" class</div>
<div class="box clickable">Click me to toggle outline</div>
</div>
<hr>
<input type="text" placeholder="I'm outlined when focused" />
<script>
// Called on an element to wrap with an outline and passed a styleObject
// the styleObject can contain the following outline properties:
// style, width, color, offset, radius, bottomLeftRadius,
// bottomRightRadius, topLeftRadius, topRightRadius
// It then creates a new div with the properties specified and
// moves the calling element into the div
// The newly created wrapper div receives the class "simulated-outline"
Element.prototype.addOutline = function (styleObject, hideOutline = true) {
var element = this;
// create a div for simulating an outline
var outline = document.createElement('div');
// initialize css formatting
var css = 'display:inline-block;';
// transfer any element margin to the outline div
var margins = ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'];
var marginPropertyNames = {
marginTop: 'margin-top',
marginBottom: 'margin-bottom',
marginLeft: 'margin-left',
marginRight: 'margin-right'
}
var outlineWidth = Number.parseInt(styleObject.width);
var outlineOffset = Number.parseInt(styleObject.offset);
for (var i = 0; i < margins.length; ++i) {
var computedMargin = Number.parseInt(getComputedStyle(element)[margins[i]]);
var margin = computedMargin - outlineWidth - outlineOffset;
css += marginPropertyNames[margins[i]] + ":" + margin + "px;";
}
element.style.cssText += 'margin:0px !important;';
// compute css border style for the outline div
var keys = Object.keys(styleObject);
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
var value = styleObject[key];
switch (key) {
case 'style':
var property = 'border-style';
break;
case 'width':
var property = 'border-width';
break;
case 'color':
var property = 'border-color';
break;
case 'offset':
var property = 'padding';
break;
case 'radius':
var property = 'border-radius';
break;
case 'bottomLeftRadius':
var property = 'border-bottom-left-radius';
break;
case 'bottomRightRadius':
var property = 'border-bottom-right-radius';
break;
case 'topLeftRadius':
var property = 'border-top-left-radius-style';
break;
case 'topRightRadius':
var property = 'border-top-right-radius';
break;
}
css += property + ":" + value + ';';
}
// apply the computed css to the outline div
outline.style.cssText = css;
// add a class in case we want to do something with elements
// receiving a simulated outline
outline.classList.add('simulated-outline');
// place the element inside the outline div
var parent = element.parentElement;
parent.insertBefore(outline, element);
outline.appendChild(element);
// determine whether outline should be hidden by default or not
if (hideOutline) element.hideOutline();
}
Element.prototype.showOutline = function () {
var element = this;
// get a reference to the outline element that wraps this element
var outline = element.getOutline();
// show the outline if one exists
if (outline) outline.classList.remove('hide-outline');
}
Element.prototype.hideOutline = function () {
var element = this;
// get a reference to the outline element that wraps this element
var outline = element.getOutline();
// hide the outline if one exists
if (outline) outline.classList.add('hide-outline');
}
// Determines if this element has an outline. If it does, it returns the outline
// element. If it doesn't have one, return null.
Element.prototype.getOutline = function() {
var element = this;
var parent = element.parentElement;
return (parent.classList.contains('simulated-outline')) ? parent : null;
}
// Determines the visiblity status of the outline, returning true if the outline is
// visible and false if it is not. If the element has no outline, null is returned.
Element.prototype.outlineStatus = function() {
var element = this;
var outline = element.getOutline();
if (outline === null) {
return null;
} else {
return !outline.classList.contains('hide-outline');
}
}
// this embeds a style element in the document head for handling outline visibility
var embeddedStyle = document.querySelector('#outline-styles');
if (!embeddedStyle) {
var style = document.createElement('style');
style.innerText = `
.simulated-outline.hide-outline {
border-color: transparent !important;
}
`;
document.head.append(style);
}
/*########################## example usage ##########################*/
// add outline to all elements with "outline-me" class
var outlineMeStyle = {
style: 'dashed',
width: '3px',
color: 'blue',
offset: '2px',
radius: '5px'
};
document.querySelectorAll('.outline-me').forEach((element)=>{
element.addOutline(outlineMeStyle, false);
});
// make clickable divs get outlines
var outlineStyle = {
style: 'double',
width: '4px',
offset: '3px',
color: 'red',
radius: '10px'
};
document.querySelectorAll('.clickable').forEach((element)=>{
element.addOutline(outlineStyle);
element.addEventListener('click', (evt)=>{
var element = evt.target;
(element.outlineStatus()) ? element.hideOutline() : element.showOutline();
});
});
// configure inputs to only have outline on focus
document.querySelectorAll('input').forEach((input)=>{
var outlineStyle = {
width: '2px',
offset: '2px',
color: 'black',
style: 'dotted',
radius: '10px'
}
input.addOutline(outlineStyle);
input.addEventListener('focus', (evt)=>{
var input = evt.target;
input.showOutline();
});
input.addEventListener('blur', (evt)=>{
var input = evt.target;
input.hideOutline();
});
});
</script>
In closing, let me reiterate, that implementing this approach may require more styling than what I have included in my demos, especially if you have already styled the element you want outlined.
outline-style: auto has had full browser support for ages now.
Shorthand is:
outline: auto blue;
This let's you set a custom color, but not a custom thickness, unfortunately (although I think the browser default thickness is a good default).
You can also set a custom outline-offset when using outline-style: auto.
outline: auto blue;
outline-offset: 0px;
you can use box-shadow instead of outline like this
box-shadow: 0 0 1px #000000;
border-radius: 50px;
outline: none;
Try using padding and a background color for the border, then a border for the outline:
.round_outline {
padding: 8px;
background-color: white;
border-radius: 50%;
border: 1px solid black;
}
Worked in my case.
I just set outline transparent.
input[type=text] {
outline: rgba(0, 0, 0, 0);
border-radius: 10px;
}
input[type=text]:focus {
border-color: #0079ff;
}
I like this way.
.circle:before {
content: "";
width: 14px;
height: 14px;
border: 3px solid #fff;
background-color: #ced4da;
border-radius: 7px;
display: inline-block;
margin-bottom: -2px;
margin-right: 7px;
box-shadow: 0px 0px 0px 1px #ced4da;
}
It will create gray circle with wit border around it and again 1px around border!

Categories

Resources