How to Apply a Clipping Path to a DIV Container

2019-02-15 09:50发布

问题:

I'm attempting to build a kind of progress bar, similar to what Chris Coyier does here: https://css-tricks.com/css3-progress-bars/, except that I want to apply a SVG clipping path to the surrounding div element.

For example, I want to apply a SVG clipping path to .rating-stars:

<div class="rating-stars">
    <span class="rating"></span>
</div>

You can see what I have so far here: http://codepen.io/rleichty/pen/GrWmRR

It doesn't work in Safari for some reason, and I'm also not sure how to resize the SVG clipping path (if that's even possible).

Does anyone know how I might accomplish this, or perhaps have a better solution?

回答1:

Figured it out. Here's the final code here: http://codepen.io/rleichty/pen/zNZajM

HTML:

<div class='rating-container'>
    <div class='rating-stars'>
        <span class='rating'></span>
    </div>
</div>

<svg width='0' height='0'>
    <defs>
        <clipPath id='svgStars' clipPathUnits = 'objectBoundingBox'>
            <polygon points=".80 .073 .738 .118 .762 .19 .70 .145 .638 .19 .662 .118 .60 .073 .538 .118 .562 .19 .50 .145 .438 .19 .462 .118 .40 .073 .338 .118 .362 .19 .30 .145 .238 .19 .262 .118 .20 .073 .138 .118 .162 .19 .10 .145 .038 .19 .062 .118 0 .073 .076 .073 .10 0 .124 .073 .276 .073 .30 0 .324 .073 .476 .073 .50 0 .524 .073 .676 .073 .70 0 .724 .073 .876 .073 .90 0 .924 .073 1 .073 .938 .118 .962 .19 .90 .145 .838 .19 .862 .118 "/>
        </clipPath>
    </defs>
</svg>

CSS:

$ratingHeight: 100px;

.rating-container {
    position: relative;
    width: calc((100 / 19) * #{$ratingHeight});
    height: $ratingHeight;
}

.rating-stars {
    position: absolute;
    width: 100%;
    height: 0%;
    padding-bottom: 100%;
    background: lightgray;
    -webkit-clip-path: url(#svgStars);
    clip-path: url(#svgStars);
    -webkit-clip-path: polygon(80% 7.3%, 73.8% 11.8%, 76.2% 19%, 70% 14.5%, 63.8% 19%, 66.2% 11.8%, 60% 7.3%, 53.8% 11.8%, 56.2% 19%, 50% 14.5%, 43.8% 19%, 46.2% 11.8%, 40% 7.3%, 33.8% 11.8%, 36.2% 19%, 30% 14.5%, 23.8% 19%, 26.2% 11.8%, 20% 7.3%, 13.8% 11.8%, 16.2% 19%, 10% 14.5%, 3.8% 19%, 6.2% 11.8%, 0 7.3%, 7.6% 7.3%, 10% 0, 12.4% 7.3%, 27.6% 7.3%, 30% 0, 32.4% 7.3%, 47.6% 7.3%, 50% 0, 52.4% 7.3%, 67.6% 7.3%, 70% 0, 72.4% 7.3%, 87.6% 7.3%, 90% 0, 92.4% 7.3%, 100% 7.3%, 93.8% 11.8%, 96.2% 19%, 90% 14.5%, 83.8% 19%, 86.2% 11.8%);
    clip-path: polygon(80% 7.3%, 73.8% 11.8%, 76.2% 19%, 70% 14.5%, 63.8% 19%, 66.2% 11.8%, 60% 7.3%, 53.8% 11.8%, 56.2% 19%, 50% 14.5%, 43.8% 19%, 46.2% 11.8%, 40% 7.3%, 33.8% 11.8%, 36.2% 19%, 30% 14.5%, 23.8% 19%, 26.2% 11.8%, 20% 7.3%, 13.8% 11.8%, 16.2% 19%, 10% 14.5%, 3.8% 19%, 6.2% 11.8%, 0 7.3%, 7.6% 7.3%, 10% 0, 12.4% 7.3%, 27.6% 7.3%, 30% 0, 32.4% 7.3%, 47.6% 7.3%, 50% 0, 52.4% 7.3%, 67.6% 7.3%, 70% 0, 72.4% 7.3%, 87.6% 7.3%, 90% 0, 92.4% 7.3%, 100% 7.3%, 93.8% 11.8%, 96.2% 19%, 90% 14.5%, 83.8% 19%, 86.2% 11.8%);
}

.rating {
    position: absolute;
    display: block;
    width: 50%;
    height: 100%;
    background-color: orange;
}

You can adjust the height of the stars by adjusting this SCSS variable $ratingHeight: 100px; You don't need to use the variable, but it makes it simpler to adjust only one value (you could also flip it around so that the width is prioritized).

I found a lot of help here https://sarasoueidan.com/blog/css-svg-clipping/ as she notes:

One important thing to note here is that when you use the objectBoundingBox value, the coordinates specified for the contents of the <clipPath> must be in the range [0, 1]—the coordinate system becomes a unit system, and the coordinates of the shapes inside a clipPath have to be fractions in that range.

So I had to adjust the SVG coordinates to fractions.

In order to keep the aspect ratio of a percentage based clipping path, I found help out here How do I keep the aspect ratio of a percentage based clipping path? and simply used the padding-bottom trick.

Now all you need to do is use jQuery to dynamically set the width of the .rating element to represent a star rating. In my case, I'll be pulling data from Goodreads.

Personally I like this method as opposed to having separate star images for every single rating. It's simpler and more lightweight, as well as being vector based and responsive.