svg + Sprite sheet + d3 + clipPath + position + size

隐身守侯 提交于 2019-12-20 23:29:49

问题


Need to apologize in advance: for length and for my ignorance. I'm trying to teach myself new concepts: d3.js and sprite sheets. The sprite sheet concept is simple to understand, but I'm confused how to integrate this into d3. Basically what I want to do is to select the sprite that I want to use as an image from the sprite sheet and then use d3 to display this selected sprite somewhere else on the page, and most likely multiple copies of the same sprite.

The actual sprite sheet for reference (see disclaimer below):

Here are the issues: 1) I add the sprite sheet to my html, hard code the <clipPath> for now, this displays the particular sprite I want, however, the dimensions/positioning of the sprite is just as though the entire sprite sheet is displayed. How can I only "capture" the sprite itself, not just hide the unused ones? In the image below, I want to use a single "icon" in a d3 mouseover event (part 2).

Modifying this example: SO: Display CSS image sprite in SVG without using foreignObject

HTML

<svg id="mySvg1" width="100%" height="100%">
    <defs>
        <clipPath id="c">
            <rect x="135" y="0" width="150" height="150"/>
        </clipPath>
    </defs>
        <image transform="scale(1.0)" x="0" y="0" width="550" height="420" xlink:href="static/img/iconSheet.png" clip-path="url(#c)"/>
<svg>

Result

2) I can use <pattern> to determine an image to show in a d3 object/event. Like show a graphic in a rectangle. But this doesn't seem to work for large images (sprite sheets)? It becomes weird and blurry if I try to use the sprite sheet itself and its native dimensions in the pattern. If we solve part 1 we can probably ignore part 2, but this would be good to understand for general knowledge/future use.

Modifying this example: SO: Adding an image within a circle object in d3 javascript?

HTML

<svg id="mySvg" width="550" height="420">
  <defs id="mdef">
    <pattern id="image" x="0" y="0" height="550" width="420">
      <image transform="scale(1.0)" x="0" y="0" width="550" height="420" xlink:href="static/img/iconSheet.png"></image>
    </pattern>
  </defs>
</svg>

Javascript:

var svgContainer = d3.select("div#content-main").append("svg")
                                    .attr("width", 740)
                                    .attr("height", 760)
                                    .attr("class", "mySvg")
                                    .style("border", "none");

svgContainer.append("rect")
         .attr("class", "logo")
         .attr("x", 0)
         .attr("y", 0)
         .attr("width", 550)
         .attr("height", 420)
         .style("fill", "transparent")  
         .style("stroke", "black")     
         .style("stroke-width", 0.25)     
         .on("mouseover", function(){ 
               d3.select(this)
                   .style("fill", "url(#image)");
         })
          .on("mouseout", function(){ 
               d3.select(this)
                   .style("fill", "transparent");
         });

Result

3) IF there is a more efficient way to accomplish this I am open to suggestion. I'm simply sticking with the d3 model because I've already rendered an svg object and I just need to add things to it.

DISCLAIMER: The icons are not my work! I am using these icons for educational purposes only. The author's link is here: Fitness Icons


回答1:


I'm not sure what's going on with <pattern> example, but the problem with your <image> element is that you're not translating the image so that the icon you want is at the (0,0) point of the SVG.

This is what you need:

<svg id="mySvg1" width="100%" height="100%" viewBox="0 0 150 150">
    <defs>
        <clipPath id="c">
            <rect x="135" y="0" width="150" height="150"/>
        </clipPath>
    </defs>
        <image transform="translate(-135,0)" width="550" height="420" 
            xlink:href="static/img/iconSheet.png" clip-path="url(#c)"/>
<svg>

Of course, if you're going to be making lots of icons and using them multiple places, I would suggest:

  1. defining the icons within a <defs> element, and then referencing them when needed with <use> elements;
  2. using a <use> element to position the image in each icon, so you only have to define the image url, height, and width once;
  3. nesting each image/use element within a <g> element, and applying the clipping path to it, so that you only have to define the clipping path once (assuming all icons are the same size).

Example here: http://codepen.io/AmeliaBR/pen/mwzBD

Key code for defining the icons:

<svg class="icon-defs">
  <defs>
    <!-- The icons are defined in an SVG <defs> element;
        it could be in a different file,
        since the icons will be referenced by url. -->

    <!-- One clipping path defines icon size -->
    <clipPath id="icon-cp" >
        <rect x="0" y="0" width="150" height="100" />
    </clipPath>

    <!-- One image element imports the file -->
    <image id="icon-sprite" width="969" height="293" 
           xlink:href="http://i.stack.imgur.com/TPx5h.png" />

    <!-- Each icon fragment uses the same image 
         with a different translation -->
    <g id="icon1" clip-path="url(#icon-cp)">
        <use xlink:href="#icon-sprite" 
            transform="translate(0,0)" />
    </g>
    <g id="icon2" clip-path="url(#icon-cp)">
        <use xlink:href="#icon-sprite" 
            transform="translate(-240,0)" />
    </g>
    <g id="icon3" clip-path="url(#icon-cp)">
        <use xlink:href="#icon-sprite" 
            transform="translate(-240,-193)" />
    </g>   
 </defs>

Then you reference the icons like this:

<svg class="icon" viewBox="0 0 150 100" height="4em" width="6em">
        <use xlink:href="#icon3"/>
</svg>

The viewBox attribute sets the internal dimensions for laying out the image and will be the same every time you use the icon; the height and width can be anything you want (although scaling down will of course look better than scaling up). If the height/width ratio doesn't match the icon, it will be squished or stretched, but you can prevent that with a preserveAspectRatio attribute.

Now, on to d3. It will probably be easiest to define the SVG fragments that represent the icons ahead of time, possibly in a separate file, although you could construct that DOM dynamically. When you actually want to insert an icon, you

For example, to add an inline icon image at the end of every element with the class "warning", you would do something like this:

d3.selectAll(".warning")
    .append("svg")
      .attr("viewBox", "0 0 "+ iconWidth + " " + iconHeight)
      .style("display", "inline")
      .style("height", "1em")
      .style("width", (iconWidth/iconHeight) + "em")
    .append("use")
      .attr("xlink:href", "#warning");

Of course, if you're using d3, you've probably got some data variable that tells you which icon to use, instead of a class, but you get the idea.




回答2:


I think a much simpler way to clip and position the icon is by using a nested <svg> with an appropriate viewBox.

<svg width="100%" height="100%">
                              
    <!-- Repeat this for ever icon instance you want.
         Just change x and y attributes to set position of the icon in your SVG,
         and minX,minY (first two coords) of viewBox to select icon from sprite sheet. -->
    <svg x="0" y="0" width="150px" height="150px" viewBox="135 0 150 150">
        <image width="550px" height="420px" xlink:href="http://i.stack.imgur.com/qAO2h.png" />
    </svg>
  
</svg>


来源:https://stackoverflow.com/questions/21193447/svg-sprite-sheet-d3-clippath-position-size

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!