Summary: I'm trying to create a canvas of randomized rock climbing holds using vector graphics generated with properties such as color, rotation, size and path values.
To add depth to these I'm trying to add a sort of randomized shadow to them to show that one or more sides of the shape are raised from the background.
Question:
I've been able to apply this filter onto the svg however as you can see on the below image "Light Filter" I get this white effect bleeding out to the edge of the svg element.
I'd like to find a way to keep that raised effect and have the color show or find a new way to show shadow randomized to each edge of the svg path?
You can find the filter code in the function: addFilter
You can disable the filter effect by commenting out the function addFilter(); and applyFilter();
No Filter:
Light Filter:
//create a filter for the svg copying the rough paper filter and apply it to the svg
var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "roughpaper");
filter.setAttribute("x", "0%");
filter.setAttribute("y", "0%");
filter.setAttribute("width", "100%");
filter.setAttribute("height", "100%");
var feDiffuseLighting = document.createElementNS("http://www.w3.org/2000/svg", "feDiffuseLighting");
feDiffuseLighting.setAttribute("in", "noise");
feDiffuseLighting.setAttribute("lighting-color", "#ffffff");
feDiffuseLighting.setAttribute("surfaceScale", "2");
var feDistantLight = document.createElementNS("http://www.w3.org/2000/svg", "feDistantLight");
feDistantLight.setAttribute("azimuth", "45");
feDistantLight.setAttribute("elevation", "60");
feDiffuseLighting.appendChild(feDistantLight);
filter.appendChild(feDiffuseLighting);
document.getElementById("svg-0").appendChild(filter);
body {
background-color: #333;
overflow: hidden;
}
#svg-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-element {
position: absolute;
width: 150px;
height: 150px;
}
<div id="svg-container">
<svg
viewBox="0 0 200 200"
preserveAspectRatio="none"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
class="svg-element"
id="svg-0"
filter="url(#roughpaper)"
>
<path
d="M27.8,-30.1C31.8,-29.8,27.8,-17,30.3,-5C32.9,7,42,18.2,38.3,19.7C34.5,21.1,18,12.7,4.6,21.4C-8.9,30.1,-19.3,55.9,-25.4,58.5C-31.5,61.2,-33.3,40.7,-44.1,24.4C-54.8,8,-74.4,-4.3,-75.5,-15.9C-76.6,-27.6,-59.1,-38.6,-43.4,-36.8C-27.7,-34.9,-13.9,-20.3,-1,-19.1C11.9,-17.9,23.9,-30.3,27.8,-30.1Z"
transform="translate(100, 100)"
class="path"
id="path-0"
style="fill: rgb(106, 76, 147)"
></path>
</svg>
</div>
Diffuse light effects should be multiplied with the original, otherwise you'll see the the lighting color rather than the combination of original color + lighting. So just add a feBlend with a multiply - like so. Update: and then add a feComposite/in to "clip to self" - so you don't see the background lit with the light as well.
//create an array with multiple svg paths
var svgPaths = [
{
path: "M27.8,-30.1C31.8,-29.8,27.8,-17,30.3,-5C32.9,7,42,18.2,38.3,19.7C34.5,21.1,18,12.7,4.6,21.4C-8.9,30.1,-19.3,55.9,-25.4,58.5C-31.5,61.2,-33.3,40.7,-44.1,24.4C-54.8,8,-74.4,-4.3,-75.5,-15.9C-76.6,-27.6,-59.1,-38.6,-43.4,-36.8C-27.7,-34.9,-13.9,-20.3,-1,-19.1C11.9,-17.9,23.9,-30.3,27.8,-30.1Z",
},
{
path: "M36.5,-45.3C49.8,-32.4,64.8,-23.2,69,-10.5C73.3,2.2,66.7,18.5,58.3,34.1C49.8,49.8,39.4,64.8,23.8,74.4C8.1,84,-12.7,88.2,-22.9,77.9C-33,67.7,-32.3,43,-35.4,26.1C-38.5,9.1,-45.3,0,-46.2,-10.5C-47.2,-21,-42.3,-32.7,-33.6,-46.4C-24.9,-60.1,-12.5,-75.7,-0.4,-75.2C11.6,-74.7,23.2,-58.1,36.5,-45.3Z",
}
];
var colors = ["#FF595E", "#FFCA3A", "#8AC926", "#1982C4", "#6A4C93"];
//create a function to apply properties to each svg element
function holdProps() {
//based on the id of the svg element, re-position the svg element on the screen
var svgElements = document.getElementsByClassName("svg-element");
//set the position of the svg element to the top left
for (var i = 0; i < svgElements.length; i++) {
svgElements[i].style.position = "absolute";
svgElements[i].style.top = "0";
svgElements[i].style.left = "0";
}
//console log the bounding box of each svg element
for (var i = 0; i < svgElements.length; i++) {
var svg = svgElements[i];
console.log(svg.getBoundingClientRect());
}
}
//create a function to apply properties to each svg elements path value
function holdPaths() {
//create a path and append it to each svg element
$(".svg-element").each(function () {
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
//set attributes to the above created path
path.setAttribute(
"d",
svgPaths[Math.floor(Math.random() * svgPaths.length)].path
);
path.setAttribute("transform", "translate(100, 100)");
path.setAttribute("class", "path");
path.setAttribute("id", "path-" + $(this).attr("id").split("svg-")[1]);
this.appendChild(path);
});
//If a path id is clicked change the path to a random path with a random color
$(".svg-element").click(function () {
var path = svgPaths[Math.floor(Math.random() * svgPaths.length)].path;
var color = colors[Math.floor(Math.random() * colors.length)];
$(this).find("path").attr("d", path);
$(this).find("path").css("fill", color);
});
}
//create a function to apply random colors to each svg element
function colorHold() {
$(".svg-element path").each(function () {
var color = colors[Math.floor(Math.random() * colors.length)];
$(this).css("fill", color);
//stroke white width 5 if the svg is hoverd over
$(this).hover(function () {
$(this).css("stroke", "white");
$(this).css("stroke-width", "5");
}
//reset stroke to black and stroke width to 1 if the svg is not hovered over
, function () {
$(this).css("stroke", "black");
$(this).css("stroke-width", "0");
}
);
});
}
//create feDistantLight and a fePointLight to each svg element
function addFilter() {
var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "roughpaper");
filter.setAttribute("x", "0%");
filter.setAttribute("y", "0%");
filter.setAttribute("width", "100%");
filter.setAttribute("height", "100%");
var feGauss= document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
feGauss.setAttribute("stdDeviation", "4");
feGauss.setAttribute("result", "blur");
filter.appendChild(feGauss);
var feDiffuseLighting = document.createElementNS("http://www.w3.org/2000/svg", "feDiffuseLighting");
feDiffuseLighting.setAttribute("in", "blur");
feDiffuseLighting.setAttribute("lighting-color", "#ffffff");
feDiffuseLighting.setAttribute("surfaceScale", "6");
var feDistantLight = document.createElementNS("http://www.w3.org/2000/svg", "feDistantLight");
feDistantLight.setAttribute("azimuth", "235");
feDistantLight.setAttribute("elevation", "50");
feDiffuseLighting.appendChild(feDistantLight);
filter.appendChild(feDiffuseLighting);
var feBlend= document.createElementNS("http://www.w3.org/2000/svg", "feBlend");
feBlend.setAttribute("mode", "multiply");
feBlend.setAttribute("in2", "SourceGraphic");
filter.appendChild(feBlend);
var feComp= document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
feComp.setAttribute("operator", "in");
feComp.setAttribute("in2", "SourceGraphic");
filter.appendChild(feComp);
document.getElementsByTagName("svg")[0].appendChild(filter);
}
//apply the filter to each svg element
function applyFilter() {
var svgElements = document.getElementsByClassName("svg-element");
for (var i = 0; i < svgElements.length; i++) {
svgElements[i].setAttribute("filter", "url(#roughpaper)");
}
}
//on load run the functions
$(document).ready(function () {
//create a path for each svg element
holdPaths();
//apply properties to each svg element
holdProps();
//apply random colors to each svg element
colorHold();
//Disable these two to remove the filter
//create a filter to each svg element
addFilter();
//apply the filter to each svg element
applyFilter();
});
body {
background-color: #333;
overflow: hidden;
}
#svg-container {
/* full size of page*/
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-element {
position: absolute;
}
svg path {
transition: 0.2s;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="svg-container">
<!-- Create SVG -->
<svg id="svg-01" class="svg-element" width="100%" height="100%" viewBox="0 0 200 200" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
</svg>
</div>
I'm having trouble making a SVG text element with javascript. The problem is that it is not scaling like it should when window resizes.
Look at this fiddle:
https://jsfiddle.net/cduL72mf/2/
Why is the javscript-generated "SVGtext 2" not behaving like "SVGtext 1", what I can see the output is exactly the same? What am I missing?
var xmlns = 'http://www.w3.org/2000/svg';
var svgelement = document.createElementNS(xmlns, 'svg');
svgelement.id='svg2';
svgelement.setAttribute('viewbox', '0 0 300 200');
document.body.appendChild(svgelement);
var svgtext = document.createElementNS(xmlns, 'text');
svgtext.id='text2';
svgtext.setAttribute('x', '56');
svgtext.setAttribute('y', '74');
svgtext.setAttribute('font-size', '33');
var textnode = document.createTextNode('SVGText 2');
svgtext.appendChild(textnode);
svgelement.appendChild(svgtext);
svg {
width: 100%;
height: 300px;
}
<svg id="svg1" viewbox="0 0 300 200">
<text id="text1" x="56" y="74" font-size="33">SVGText 1</text>
</svg>
viewbox should be viewBox. It is case-sensitive.
The lower-case version in the other SVG ("svg1") is accepted because the HTML parser is more forgiving. It corrects that attribute name for you.
var xmlns = 'http://www.w3.org/2000/svg';
var svgelement = document.createElementNS(xmlns, 'svg');
svgelement.id='svg2';
svgelement.setAttribute('viewBox', '0 0 300 200');
document.body.appendChild(svgelement);
var svgtext = document.createElementNS(xmlns, 'text');
svgtext.id='text2';
svgtext.setAttribute('x', '56');
svgtext.setAttribute('y', '74');
svgtext.setAttribute('font-size', '33');
var textnode = document.createTextNode('SVGText 2');
svgtext.appendChild(textnode);
svgelement.appendChild(svgtext);
svg {
width: 100%;
height: 300px;
}
<svg id="svg1" viewbox="0 0 300 200">
<text id="text1" x="56" y="74" font-size="33">SVGText 1</text>
</svg>
I'm trying to create a function that will measure how big a text element will be in a SVG element. The code examples I found at Stack Overflow does not work and gives a width of zero. If I delay the measurement I can get the text, but not right away. How is this solved?
var messureSVGtext = function(text, svg, options){
var text = document.createElementNS(svgns, 'text');
text.style.fontFamily = options.font;
text.setAttribute("style",
"font-family:" + options.font + ";" +
"font-size:" + options.fontSize + "px;"
);
var textNode = document.createTextNode(text);
text.appendChild(textNode);
svg.appendChild(text);
// This does not work
console.log(text.clientWidth);
//This does
setTimeout(function(){
console.log(text.clientWidth);
}, 100);
}
You can get the "computed style" of an element and then check the width & height from that.
Give the element an id attribute and after it is appended to the DOM, try this:
var elem1 = document.getElementById("text_elem");
var style = window.getComputedStyle(elem1, null);
console.log(style.width, style.height);
Working example
SVG
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="640"
height="480"
viewBox="0 0 640 480"
style="display:block">
<text id="svg_text" x="20" y="50" style="font-family:Arial; font-size:36px; fill:#BADA55">Hello World!</text>
</svg>
JavaScript
function getCompStyle(oid, cbf)
{
var obj, stl, itv;
obj = document.getElementById(oid);
itv = setInterval(function()
{
stl = window.getComputedStyle(obj, null);
if (stl && (stl.width.indexOf('0') != 0))
{
clearInterval(itv);
cbf(stl);
}
},0);
}
getCompStyle('svg_text', function(style)
{
console.log(style.width);
});
To use the example, place the SVG in your HTML <body> and the JavaScript in a <script> tag below the SVG - also in the <body>.