Masked SVG flickering when changing currentColor - css

To supplement my icon set, I'm using SVG's and currentColor.
However, I'm seeing some issues with flickering when the (font)color changes or there's animation on the page.
Example of color change on hover causes flicker
body{
color:green;
font-size:50px;
}
.dynamicSVG {
/*
Allows us to colour SVGs using mask, the colour will follow the text colour
NOTE: doesn't work well with SVG's with lots of colors as you lose the contrast
*/
height: 1em;
min-width: 1em;
display: inline-block;
-webkit-mask: var(--src) no-repeat 50% 50%;
mask: var(--src) no-repeat 50% 50%;
-webkit-mask-position: left;
mask-position: left;
-webkit-mask-size: contain;
mask-size: contain;
background-color: currentColor;
vertical-align: middle;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.dynamicSVG > img {
/*
Here I use an child img element inside the i.dynamicSVG.
It uses the style SRC variable to remove the need to set the URL twice in HTML, a nice CSS hack.
We do this to load in the image and ensure the dynamic SVG is the correct size and dimensions...
Which it wouldn't have been as we're using background + mask for the dynamic icon
It does mean the image will be called twice, but due to caching it shouldn't be an issue and isnt visible to the user
*/
content: var(--src);
height: 100%;
visibility: hidden;
/*Ensure the max height isn't overridden*/
max-height: 100% !important;
vertical-align:top;
}
.dynamicSVG:hover{
color:red;
}
<div class="dynamicSVG" style="--src:url('http://upload.wikimedia.org/wikipedia/fr/c/c8/Twitter_Bird.svg')"><img /></div>
<span>< Hover Me</span>
I've read some articles which suggest -webkit-translate-3d, -webkit-backface-visibility,-webkit-transform-style, none of which resolves my issue.
Essentially I use a CSS mask to colorise the SVG to my font colour.
The child img allows it to size correctly relative to the height.
However, during transitions or in particular changing the font colour (and in turn the mask colour when using currentColor, the image is either hiding completely until the transition is complete or flickers.
The question is firstly, is there a better way of linking to an SVG (without embedding the XML) and using CSS to set the colour, or if there's no better solution, how can we stop the flickering / hiding during transitions.
I've mainly noticed it in chrome in my application, but in this example we can see it occur in modern edge too.

Related

How can I make a single run-of-text outlined in some areas and a solid-fill in other areas?

I recently came across this dribbble/landing page concept with hollow/filled text.
First off I'm not entirely sure if this concept could be recreated in CSS.
A bit of Google did lead me to CSS text masks, but I wasn't able to find any post that can really recreate this effect.
How would I be able to reconstruct the hollow/filled text, depending if the background behind the text has an image or not?
It can be done in pure-HTML+CSS (without any SVG).
And it can be done in pure SVG too.
The SVG-only option will be simpler because SVG has more powerful blending and masking capabilities that aren't (yet) able to be used by HTML elements (CSS boxes).
I decided to do it for HTML+CSS because I felt like a challenge.
This HTML version is animated and the text is fully selectable.
This HTML+CSS version can be simplified further by using mask-image: element(#target) which means we don't need the mask-only text, unfortunately Chrome doesn't seem to support element() yet (but Firefox does, though).
A minor wart is the text-stroke outline does not exactly line-up with the solid white text (at least in Chrome on Windows 10 with my computer's version of Helvetica at 96dpi, but at 192dpi (2x, aka Retina)) in the same browser and computer it looks perfect.
Here's how it looks at different points in the animation on my machine at 96dpi:
The example implementation below works in the following browsers (at the time of writing):
Chrome 98
Edge 98
Firefox 97
macOS Safari Technology Preview (Release 137)
It kinda works in macOS Safari 15.2, but you have to click on it first for some reason.
It doesn't work in iOS Safari 15.1 (only the outlined text is visible, the solid text doesn't render at all).
Because it does work in the Safari Preview browser it should work in the next update to iOS Safari and macOS Safari, however.
body {
background-color: #dbdac2;
--solid-white: linear-gradient(white,white);
}
#container,
#container > #div1 {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-size:
153px 302px,
148px 302px,
154px 302px;
background-position:
131px 94px,
309px 28px,
480px 94px;
background-repeat:
no-repeat,
no-repeat,
no-repeat;
animation: moveImages 2s infinite;
animation-direction: alternate;
}
#container {
border: 1px solid white;
position: relative;
width: 711px;
height: 440px;
/* These are the 3 photo images, rendered as separate background-image layers: */
background-image:
url( "https://i.stack.imgur.com/hmwyh.png" ),
url( "https://i.stack.imgur.com/JeHEg.png" ),
url( "https://i.stack.imgur.com/pVgz6.png" );
}
#container p {
margin: 0;
position: static;
padding-top: 192px;
padding-left: 62px;
overflow: hidden;
font-family: Helvetica;
font-size: 99px;
letter-spacing: -2px;
font-weight: 600;
}
#container > #pStroke {
text-stroke: 1px white;
-webkit-text-stroke: 1px white;
color: transparent;
}
#container > #div1 {
/* #div1's background-image layers must match #container's: */
background-image:
var(--solid-white),
var(--solid-white),
var(--solid-white);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
#keyframes moveImages {
/* The `#keyframes from {}` rule is optional, btw. */
to {
background-size:
53px 302px,
58px 302px,
154px 302px;
background-position:
431px 94px,
209px 28px,
280px 194px;
}
}
<div id="container">
<div id="div1">
<p id="pWhite">Fashion Give<br />Impression.</p>
</div>
<p id="pStroke">Fashion Give<br />Impression.</p>
</div>
Explanation:
The div#container element has a background-image property set to 3 different sources images (using meme images in lieu of the fashion photos from your posted example).
These images can be positioned and scaled independently via the background-size and background-position properties.
Another element (#div1) overlays div#container through absolute positioning and has the same background-size and background-position properties, but uses a single solid white background image (from a linear-gradient(white,white)) instead of the photos, and then those 3 white background image layers are masked by #div1's inner <p>'s text using background-clip: text;.
This example has to use background-image: linear-gradient (or background-image: url("1x1px-white.png");) instead of background-color: white; because it needs to be repeated in 3 separate layers, whereas background-color: white; cannot be used to define a rectangular area within the background, nor can you have multiple background-color layers (even when semi-transparent).
The example also has to use DOM text to mask the white rectangles - instead of using white rectangles to mask the DOM text - because CSS-in-HTML doesn't support any kind of reversed text masking though, otherwise this would be a lot easier. I believe this is easily possible in SVG, however.
#div1's <p> element is used to position the text correctly by using only its inner padding instead of position: absolute; because positioned text cannot be used with background-clip: text;, unfortunately.
Another <p> element with a copy of the text is used for the stroked text (with text-stroke: 1px white;)
While the text content is duplicated in the HTML source, the more fiddly size and position information of the 3 images (and their respective white masks) does not need to be duplicated in the CSS, fortunately; thanks to how CSS's selectors work (as both #container and #div1 have their background-size and background-position properties set by a single CSS rule).
Possible alternative approaches:
Instead of using a repeated identically sized background-image layers for the solid white parts, a single (but horribly complicated) clip-path tracing the 3 boxes (like a single line drawn like an etch-a-sketch) could be used on #pWhite but this would not be feasibly animatable.
I think the best possible approach would be something like this:
Using 3 separate <img /> elements for the 3 photos (instead of background-image) and using absolute positioning inside a new <div id="images">.
And then the solid white text <p> (in a sibling element to div#images) would be absolute-ly positioned over div#images by z-index and masked with mask-image: element(#images);
But Chrome doesn't seem to support using element() in HTML+CSS, only Firefox does, as far as I know.
The stroked text would remain as a duplicate <p> element positioned behind the div#images.
Another advantage of this approach is that the <img/> elements can be positioned using transform: translate instead of having to go through background-position or position: absolute which would yield much better performance and framerates.
I can't think of any approaches that don't require duplicating the text content, though - at least until CSS's content: property allows elements to copying text from other elements.

backdrop-filter: opacity() does not seem to work

I am making an website on which, I am looking for an effect that I can not reproduce. I might be making it all wrong so I am going to explain the effect how I am planning on doing it but I am open for other perspectives of course.
The effect:
I have a background image (whole screen) I covered it with a dark grayish color. I also have a 100 by 100 pixels round div following my mouse pointer. My goal is to make this round div a kind of "window" that could see through the dark gray and so reveling the background image on mouse movement.
My method :
styled the background image to be z-index: 1, height: 100vh and width: 100vw.
styled the background gray color to be z-index: 2, height: 100vh, width 100vw and background-color: gray;
styled the div following my pointer to be z-index: 3, height: 100px, width:100px, border-radius: 50% and backdrop-filter: opacity(0);
Of course I have simplified everything and I can tell the backdrop-filter is working with other options like blur or grayscale... But I don't know why the opacity options seems to do nothing at all. I have read that backdrop-filter: opacity() would require other CSS settings like mix-blend-mode. But I have tried quite a few without success.
I know I am thinking only in CSS right now, hence I am open to other suggestions with or without CSS. If you need more details do not hesitate. I am going to make an example on codeSandBox and edit my post to make it easier to understand.
If you can use Js this will work
let image = document.querySelector('#image');
document.body.addEventListener('mousemove', e => {
image.style.clipPath =
`circle(100px at ${e.pageX}px ${e.pageY}px)`;
});
body {
margin: 0;
}
#background {
width: 100vw;
height: 100vh;
background: rgba(24, 24, 24);
position: fixed;
}
#image {
width: 100vw;
height: 100vh;
background: url("https://images.unsplash.com/photo-1542831371-29b0f74f9713") no-repeat;
background-size: contain;
background-position: center;
position: fixed;
clip-path: polygon(0 0);
cursor: none;
}
<div id="background"></div>
<div id="image"></div>
It works basically by updating the clip-path property when the mousemove event is fired.
Edit:
I'll assume you don't know Js, so I'll add that you can play with the circle size (and even its shape) by changing this line:
circle(100px at ${e.pageX}px ${e.pageY}px)
to
circle(75px at ${e.pageX}px ${e.pageY}px)
or even this
polygon(${e.pageX}px ${e.pageY + 25}px, ${e.pageX - 25}px ${e.pageY - 25}px, ${e.pageX + 25}px ${e.pageY - 25}px)
Be sure that the z-index is effective. Remember that it only applies on positioned elements, which mean that if your background-image and div do not have a "position" attribute it will not take the z-index into consideration.

Button out of 3 graphic parts with fluid mid-part

I have an image:
with 3 parts:
, and
I want a button with a repeating part2, so the button text (centered) is variable.
But the button text should range 50% into the other pieces.
Part1 and part3 need a min width I think, unfortunately I have no useful example.
:before and :after didn't work very well (with position:absolute or similar), because the repeat part have to be fluid between the outer parts.
Any ideas? Greetz.
A modern posibility would be using border-image.
But if you want a wider support, do it with backgrounds.
The problem is that a repeating bkg is difficult to size . So, it's best to handle it in a pseudo element
.test {
min-width: 200px;
text-align: center;
line-height: 90px;
display: inline-block;
margin: 20px;
height: 100px;
padding: 0px 20px;
font-size: 30px;
color: white;
background-image: url('//i.stack.imgur.com/mYxcX.png'), url('//i.stack.imgur.com/TlpN0.png');
background-size: auto 100%;
background-repeat: no-repeat;
background-position: left top, right top;
position: relative;
}
.test:after {
content: "";
position: absolute;
background-image: url('//i.stack.imgur.com/GMhMi.png');
background-size: auto 100%;
left: 90px;
right: 100px;
top: 0px;
bottom: 0px;
z-index: -1;
}
<div class="test">TEST</div>
<div class="test">long test</div>
<div class="test">much longer test</div>
And the same, using border image. Using this image
we will get this: (note the trick about height:0px to allow for a single image in all the left and right sides.)
.test {
display: inline-block;
margin: 20px;
height: 0px;
font-size: 30px;
border-width: 50px;
border-image-source: url(http://i.stack.imgur.com/oXiA6.png);
border-image-slice: 50% 49% 50% 50% fill;
border-image-repeat: repeat repeat;
}
<div class="test">TEST</div>
<div class="test">long test</div>
<div class="test">much longer test</div>
UPDATED and totally Changed:
Thanks to #vals comment below which let me had the "idea bulb" above my head, hence the "unless.." part in the comment.
This new solution is much cleaner in CSS and HTML, less code, no need to worry about position:absolute, no need for extra mess, just simply uses "multiple backgrounds" (1) as well as calc()(2) function with min-width too techniques. but first here's the code and comments will explain:
JS Fiddle
.test-class {
/* so that div can expand to contain the text as well as the padding */
width:auto;
/* min width = 173px left image width + 199px right image width */
/* without this it'll collapse */
min-width:372px;
padding:0 20px 0 10px; /* just to give it breathign space on sides */
line-height: 148px;
color: white;
font-size:24px;
/* no color background because the images are PNGs with alpha */
background-color: transparent;
/* setting multiple images having the middle "extendable" one as third background */
background-image: url('//i.stack.imgur.com/mYxcX.png'),
url('//i.stack.imgur.com/TlpN0.png'),
url('//i.stack.imgur.com/GMhMi.png');
/* set no repeat to all, even the extendable otherwise it'll appear behind the
other two images, instead we don't repeat it but control its size later */
background-repeat: no-repeat, no-repeat, no-repeat;
/* position each image to its corresponding position, the 46.5% for the middle
image is because the left-side image has less width than the one on the right */
background-position:left center, right center, 46.5% 50%;
/* finally giving the images on the sides their exact-pixel size, while for the
one on the middle we make use of calc() function, so the width size of the middle
image = full div size (100%) - the width values of the left and right image (173+199) */
background-size: 173px 148px, 199px 148px, calc(100% - 372px) 148px;
display: inline-block;
text-align:center;
}
<div class="test-class">Home</div>
<div class="test-class" style="margin-left:200px;">about company</div>
<div class="test-class">example dummy text for demo only</div>
Alternatively, as I commented, you can use the CSS Sliding Door technique which was so practical and used a lot before CSS border-radius and CSS shadow presented and simplified interfaces. another example perfect CSS sprite sliding doors button
This JS Fiddle 2 shows how to implement the sliding door method for achieving such task, while it looks kind too much wide for this images set, since the right side image has 199px width, it could be used for images with less width values.
And this JS Fiddle 3 is similar to sliding door but with :before and :after but with one issue that it has to have display:block which make it not workign for horizontal alignment but could be fixed with javascript after settign it's display to inline-block.
Also there's another way, using SVG as background image which is better first because it is scale-able especially for non linear images like the blue ink circle used in the great example by #vals .
Second benefit of using SVG is using inline SVG and because SVG is made of groups and element could be targeted with CSS just like targeting other DOM elements.
https://css-tricks.com/using-svg/
----------------------------------------------------------------------------
(1). Resources:
caniuse - Multiple backgrounds
MDN - Using CSS multiple backgrounds
(2). Resources:
caniuse CSS calc()
MDN - calc()
CSS-Tricks - A couple of use cases for calc

Make a background image transparent using CSS

I have a scenario where I need a transparent background image but I have no control over the dynamically generated images I use. For that reason Transparent PNG is out of the question. All child elements within the same div should NOT be effected and should be fully visible.
I know how to apply transparency to background colours and block level elements but can you do this for a background image?
Setting the opacity of the element with the background is a good start, but you'll see that any elements within the one whose opacity is changed will also be transparent.
The way around that is to have an element that contains the background and is transparent (opacity:0.6; filter:alpha(opacity=60)), and then float or position the container with the actual content over it.
Here's a sample of how this approach would work:
#container {
width: 200px;
postiion: relative;
}
#semitrans {
width: 100%; height: 100px;
background: #f00;
opacity: 0.6;
filter:alpha(opacity=60);
}
#hello {
width: 100%; height: 100px;
position: absolute;
top: 20px; left: 20px;
}
<div id="container">
<div id="semitrans"></div>
<p id="hello">Hello World</p>
</div>
No. Not technically. You'd have to apply a background-color in order to get this to work because you'd be fading the color and image, rather than just the image. Remember that a background image is not styleable content.
You could probably hack it by using an image instead of a background image and there a mixture of relative and absolute positioning with some z-indexing on top. But that's the only way I can think of!
IE uses filter:alpha(opacity=50); while others use opacity:.5
Just include them both.

CSS: Opacity Issue

Ok, so in my page I am showing a background image with this css:
.heroarea {
background:url(/static/images/mrd_hero_01.jpg) no-repeat;
height:450px;
}
and the copy placed over it and the container the copy is in have these styles:
.main-panel {
position: absolute;
top: 130px;
left: 380px;
background: #fff;
width: 560px;
height: 340px;
padding: 30px 30px 20px 30px;
/* CSS3 standard */
opacity:0.5;
/* for IE */
filter:alpha(opacity=50);
}
.main-panel h1 {
background: transparent;
color:#39372f;
text-align: center;
/* CSS3 standard */
opacity:1;
/* for IE */
filter:alpha(opacity=100);
}
Generally, everything is as expected. That is, the image shows where I expect it to show. main-panel shows a white back ground with a transparent background. However, the text in the h1 tag is also transparent. I can see the image from underneath showing through. How can I make this so that the h1 tag content is not opaque?
Thanks!
Opacity applies to the element, not it's background.
You either need to use a translucent image, or an rgba background colour.
There is an explanation about how to do this in a backwards compatible way. (Disclosure: My site)
Use rgba and/or transparent png. Alternatively, move the content to a separate sibling div as the background:
<div id="parent">
<div id="opacity"></div>
<div id="child">text</div>
</div>
If you use transparency on a block element it makes the child element inside transparent as well.This is how css works ! I do not think there is any way to hack out of it. What you can do it to absolutely position h1 without making it a child or use a translucent image
It looks like your text is a child of .main-panel. It will take on 50% opacity. Even though you state the text is opacity 100% will only make it 100% of 50%. You will need to layer it outside of .main-panel and place it on top.
You have to move it outside of its .main-panel parent. There's no way to override the 50% opacity that's being applied there.
Alternatively, if you're only using 50% opacity to make the mrd_hero_01.jpg background image transparent, you could convert it to a .png with 50% opacity and then you wouldn't need to set the opacity on .main-panel.

Resources