Search code examples
javascripthtmlsvgvector-graphicssvg-filters

SVG Shape Lighting


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:

enter image description here

Light Filter:

enter image description here

      //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>


Solution

  • 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>