Tingying He
Tingying He

Reputation: 137

Change position of pattern in one path but does not affect other pathes with the same pattern in svg

I have filled lots of paths with a same pattern. These paths are in the same class. I want to change the position of the pattern in one of the paths individually, to put the pattern at the place I want, but do not affect the pattern position in the other paths. How should I do this?

For example, here I have filled three rectangles with a pattern. I want to change the position of the pattern in rectA by dragging the slider. Currently in my code, it will change the position of the pattern in all three shapes at the same time.

<script src="https://unpkg.com/[email protected]/dist/d3.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<svg width="300" height="100"> 
<defs>
  <pattern id="myPattern"
           x="0" y="0" width="40" height="40"
           patternUnits="userSpaceOnUse" patternTransform="rotate(0)">

    <rect id="rotateRect" x="5" y = "5" width = "30" height = "30" fill = " #87CEFA "/>

  </pattern>
</defs>
  <rect id="rectA" class="myClass" x="0" y="0" width="100" height="100"
        style="stroke: #000000;" />   
  <rect id="rectB" class="myClass" x="100" y="0" width="100" height="100"
        style="stroke: #000000;" />   
  <rect id="rectC" class="myClass" x="200" y="0" width="100" height="100"
        style="stroke: #000000;" />   
</svg>

<p style=font-size:15px>Move Pattern in Rect A </p>
<input id="slider" type="range" min="0" max="100" value="5" step='1' >

<script>
  d3.selectAll(".myClass")
  .attr("fill", "url(#myPattern)")
  
  const slider = document.getElementById("slider")
  const myPattern = document.getElementById("myPattern")
  
  slider.oninput = function(){
    myPattern.setAttribute("patternTransform", "translate(0"+","+slider.value+") ")
  }
</script>

The result I want is like this: expected result

Upvotes: 0

Views: 69

Answers (2)

Root cause is the pattern id is a global id; the first defined id will set the pattern on all shapes.

  • So use a unique pattern id for each pattern

  • or place the <svg> in a native JavaScript Web Component (JSWC) with shadowDOM,

    so all id values are local (to shadowDOM) values:

<svg-squares></svg-squares>
<svg-squares transform="25"></svg-squares>
<svg-squares transform="66"></svg-squares>

<script>
  customElements.define("svg-squares", class extends HTMLElement {
    connectedCallback() {
      this.style.display = "inline-block";
      this.attachShadow({mode:"open"})
          .innerHTML = `
             <svg width="100" height="100"> 
              <defs>
               <pattern id="P" width="40" height="40" patternUnits="userSpaceOnUse">
                <rect x="5" y="5" width ="30" height="30" fill="#87CEFA"/>
               </pattern>
             </defs>
             <rect fill="url(#P)" width="100" height="100" stroke="black" x="0" y="0"/>   
            </svg>` + 
            // slider
            `<br><input type="range" min="0" max="100" value="5"               
                        oninput="this.getRootNode().host.transform(this.value)">`;

      this.transform(this.getAttribute("transform")||0);
    }
    transform(value) {
      this.shadowRoot
          .querySelector("pattern")
          .setAttribute("patternTransform", `translate(0 ${value})`)
    }
  })
</script>

Upvotes: 2

herrstrietzel
herrstrietzel

Reputation: 17325

You can also easily create "child" patterns using the href attribute.
So these new patterns are based and more importantly linked to the initial pattern template.

<svg width="300" height="100">
  <defs>
    <pattern id="basePattern" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse" patternTransform="rotate(0)">
      <rect id="rotateRect" x="5" y="5" width="30" height="30" fill="#87CEFA" />
    </pattern>
    <!-- child pattern will inherit graphics from base pattern -->
    <pattern id="childPattern" href="#basePattern" class="childPattern" style="color:blue" patternTransform="rotate(45) scale(0.25)" />
  </defs>
  <rect id="rectA" class="rect" x="0" y="0" width="100" height="100" style="stroke: #000000;" fill="url(#basePattern)"></rect>
  <rect id="rectB" class="rect" x="100" y="0" width="100" height="100" style="stroke: #000000;" fill="url(#childPattern)"></rect>
</svg>

Actually similar to <use> references. However in many aspects quite different, since a referenced pattern can't inherit properties like fill colors.

<svg width="300" height="100">
  <defs>
    <pattern id="basePattern" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse" patternTransform="rotate(0)">

      <rect id="patternRect" x="5" y="5" width="30" height="30" fill=" #87CEFA " />

    </pattern>
  </defs>
  <rect id="rectA" class="rect" x="0" y="0" width="100" height="100" style="stroke: #000000;" />
  <rect id="rectB" class="rect" x="100" y="0" width="100" height="100" style="stroke: #000000;" />
  <rect id="rectC" class="rect" x="200" y="0" width="100" height="100" style="stroke: #000000;" />
</svg>

<p>
  A <input class="sliderPattern" id="sliderA" type="range" min="0" max="3" value="1" step='0.1'> B <input class="sliderPattern" id="sliderB" type="range" min="0" max="3" value="1" step='0.1'> C <input class="sliderPattern" id="sliderC" type="range" min="0"
    max="3" value="1" step='0.1'>
</p>
<p><button id="randomColor">Random Color (applied to base pattern)</button>
</p>

<script>
  const basePattern = document.getElementById("basePattern");
  const patternDefs = basePattern.closest('svg').querySelector('defs');
  const rects = document.querySelectorAll('.rect');
  const slider = document.getElementById("slider")
  //duplicate patterns
  for (let i = 0; i < rects.length; i++) {
    let rect = rects[i];
    let newPattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
    newPattern.id = "childPattern" + i;
    newPattern.setAttribute("href", "#basePattern");
    newPattern.classList.add("childPattern");
    rect.setAttribute('fill', `url(#childPattern${i})`);
    patternDefs.appendChild(newPattern);
  }
  const patterns = document.querySelectorAll('.childPattern');
  const sliders = document.querySelectorAll('.sliderPattern');
  sliders.forEach(function(slider, i) {
    slider.addEventListener("change", (e) => {
      let scale = e.currentTarget.value;
      let patternTransformBase = basePattern.getAttribute("patternTransform");
      patterns[i].setAttribute("patternTransform", patternTransformBase + ` scale(${scale})`)
    })
  });
  randomColor.addEventListener('click', (e) => {
    let random = Math.random();
    let newCol = `hsl(${180*random}deg 30% 60%)`;
    patternRect.style.fill = newCol
  });
</script>

Upvotes: 1

Related Questions