dom-to-image
dom-to-image copied to clipboard
when using -webkit-background-clip: text with background image image background not rendered
Use case: description, code
I have a div with this style: -webkit-text-fill-color: transparent; background: #edbc8e; background-image: url(fondo.jpg); -webkit-background-clip: text;
Expected behavior
It should render the masked text background on the canvas
Actual behavior (stack traces, console logs etc)
But it doesn't. If I use a gradient like background: linear-gradient(to bottom, #3c332b 0%,#2f2820 49%,#352f27 100%); it works, but not with an image.
Browsers
- [ ] Chrome 49+
if im using on a div: background-image: linear-gradient(90deg, $backColor 50%, transparent 50%, transparent), linear-gradient($nextdeg, $barColor 50%, $backColor 50%, $backColor);
its not rendering it, you maybe know why?
I am having this problem, but only in Chrome.
Fiddle https://jsfiddle.net/2m8dcr63/405/
Left = screenshot / Right = output of dom-to-image on Chrome Version 68.0.3440.106
.clipText { color: transparent; -webkit-text-fill-color: transparent; background-clip: text; -webkit-background-clip: text; background-size: cover; background-image: url(../img/forest.jpg) ; }
I have the same problem.
Chrome Version 71.0.3578.98
Same problem
Same problem
+1
+1
+1
any sollution ? i need to solve this problem for my current project
+1
+1
Hello, is there anyone who has found a solution to this issue?
I suspect it's got something to do with the vendor prefix since the domtoimage.toPng
works perfectly on Firefox, but not on Chrome.
Would be highly appreciated if someone knows how to fix this issue. Thank you in advance!
I resolve this issue!
reason:
chrome must use -webkit-background-clip style to expose the text clip effect;
but dom-to-image use cssText will clear -webkit-background-clip style.
it make the issue happen!
solution:
because the dom-to-image use the native API XMLSerializer to serializeToString ; we can hack by dom-xml
import { XMLSerializer } from "xmldom";
window['XMLSerializer'] = XMLSerializer; // override
let _serializeToString = XMLSerializer.prototype.serializeToString;
XMLSerializer.prototype.serializeToString = (node, isHtml, nodeFilter) => {
let string = _serializeToString(node, isHtml, nodeFilter);
if(node.hasAttribute("textClip")){ // u can use other mark to change
string = string.replace("; background-image: ","; -webkit-background-clip: text; background-image:"); // add the -webkit-background-clip
}
return string;
}
have fun~
same problem here. Havent checked @krapnikkk solution yet... but it generally doesnt work with clipped images. Not only with text
this is firefox:
Thanks @krapnikkk for finding the cause. The code wouldn't always work in all cases because it assumes background-image
isn't the first style property. I have come up with a better way:
const _serializeToString = XMLSerializer.prototype.serializeToString;
XMLSerializer.prototype.serializeToString = function (node) {
return _serializeToString
.call(this, node)
.replace(
/background-image:/g,
'-webkit-background-clip: text; background-image:', // Add the -webkit-background-clip
);
};
@yangshun I tried your solution but while clip text works, I have a div with a background (without clipping) and It doesn't get rendered anymore (neither Firefox or Chrome). If I remove your solution it works again.
This is the css:
.border {
padding: 15px 15px 0px 15px;
display: inline-block;
position: relative;
margin: 0 16px 10px 16px;
max-width: 600px;
z-index: 0;
}
.border:before {
background-image: linear-gradient(145deg, #fff, #ffffff0a);
border-radius: 15px;
bottom: 0;
content: "";
padding: 5px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
-webkit-mask-composite: destination-out;
z-index: -1;
}
Any suggestion?
@ArtemisGraphic Ah I see. My solution assumes that all the background gradient will want -webkit-background-clip
on it. In that case, I can think of two ways, but unsure if they will work:
- Add a
-webkit-background-clip: border-box
to that class to override the value-webkit-background-clip: text
added by the monkey-patchedserializeToString
- Change the implementation of
XMLSerializer.prototype.serializeToString
. Do a check onnode
first and only do the.replace()
if intended
XMLSerializer.prototype.serializeToString = function (node) {
const value = _serializeToString.call(this, node);
if (someCondition) {
return value;
}
return value.replace(
/background-image:/g,
'-webkit-background-clip: text; background-image:', // Add the -webkit-background-clip
);
};
@yangshun the first solution does not work unfortunately, I tried the second but can't think of a condition that would exclude the replace effect only for classes with clip. I tried value.hasAttribute("textClip")
but I get "it's not a function" error.
@ArtemisGraphic value
is a string. So you can only use string methods on it.
@krapnikkk I'm trying to use your script but I get 'serializeToString' called on an object that does not implement interface XMLSerializer.' I'm using dom-to-image on the client, do you have any suggestion?
Thanks @krapnikkk for finding the cause. The code wouldn't always work in all cases because it assumes
background-image
isn't the first style property. I have come up with a better way:const _serializeToString = XMLSerializer.prototype.serializeToString; XMLSerializer.prototype.serializeToString = function (node) { return _serializeToString .call(this, node) .replace( /background-image:/g, '-webkit-background-clip: text; background-image:', // Add the -webkit-background-clip ); };
Your solution works, however this removes the font-family from the text I try to render. The finished result as a clipped background, but the "Times" font.
Any idea on how to fix that?
Thanks @krapnikkk for finding the cause. The code wouldn't always work in all cases because it assumes
background-image
isn't the first style property. I have come up with a better way:const _serializeToString = XMLSerializer.prototype.serializeToString; XMLSerializer.prototype.serializeToString = function (node) { return _serializeToString .call(this, node) .replace( /background-image:/g, '-webkit-background-clip: text; background-image:', // Add the -webkit-background-clip ); };
Nice.. thank u bro..
Modify the dom-to-image.js source code:
return new XMLSerializer().serializeToString(node);
=>
return new XMLSerializer().serializeToString(node).replace(/background-image/gi, "-webkit-background-clip:text;background-image");
cause some broswer do not support background-clip in 'background' style well.
UPDATE BASE 2.6.0
- inject to dom-to-image.js
const SPECIAL_BACKGROUND_STYLE = ['background-clip', 'background-color'];
const _serializeToString = XMLSerializer.prototype.serializeToString;
XMLSerializer.prototype.serializeToString = function (node) {
const res = _serializeToString
.call(this, node)
.replace(
/-webkit-text-fill-color:/g,
'-webkit-background-clip: text; -webkit-text-fill-color:', // Add the -webkit-background-clip
);
return res;
};
- update processClone processing Promise link
return Promise.resolve()
.then(cloneStyle)
.then(clonePseudoElements)
.then(copyUserInput)
.then(fixSvg)
.then(fixBackground) // add this line
.then(function () {
return clone;
});
- inject new function into function processClone
// add style use cssText
function fixBackground() {
const originalStyle = window.getComputedStyle(original);
const cloneStyle = clone.style;
SPECIAL_BACKGROUND_STYLE.forEach((name: string) => {
const value = originalStyle.getPropertyValue(name);
if (value) {
cloneStyle.cssText += `${name}: ${value} !important;`;
}
});
}
- update function cloneStyle()
function cloneStyle() {
copyStyle(window.getComputedStyle(original), clone.style);
function copyStyle(source, target) {
if (source.cssText) target.cssText = source.cssText;
else copyProperties(source, target);
function copyProperties(source, target) {
util.asArray(source).forEach(function (name) {
if(!SPECIAL_BACKGROUND_STYLE.includes(name)) { // skip special style
target.setProperty(
name,
source.getPropertyValue(name),
source.getPropertyPriority(name)
);
}
});
}
}
}