cssmudblazor

Is there a way to crop the display of a SVG using CSS?


I am creating a component similar to MudRating to display the average of the ratings an item is given. So unlike MudRating which only allows integer values of 1 - 5, this will generally get a rating that's a double. For example 3.21 or 4.88.

At present MudBlazor only has empty, half, and full star. So the ratings have to be displayed as:

enter image description here

And these are rendered (RatingAverage.razor) as:

@for (var i = 0; i < numFullStars; i++)
{
    <MudIcon Icon="@Icons.Material.Filled.Star" Color="Color.Warning" />
}

@if (showHalfStar)
{
    <MudIcon Icon="@Icons.Material.Filled.StarHalf" Color="Color.Warning" />
}

@for (var i = startEmptyStars; i < 5; i++)
{
    <MudIcon Icon="@Icons.Material.Filled.StarOutline" Color="Color.Warning" />
}

Here's my question. Is there a way to render the partial star as:

<MudIcon Icon="@Icons.Material.Filled.StarOutline" Color="Color.Warning" />
<MudIcon Crop="0,0, @width ,1" Position="OnTopStarOutline" Icon="@Icons.Material.Filled.Star" Color="Color.Warning" />

where:

Is there a way to accomplish this? And if so, how? And I assume this is a CSS question. But if there's another way, please enlighten me.


Solution

  • Use clip path. I can't use MudIcon in snippet so these round <span>s are only for demonstration. In your code just set --width and apply the clip-path on your icons:

    .icon {
      display: inline-block;
      width: 1.5em;
      height: 1.5em;
      position: relative;
    }
    
    .icon::before {
      content: '';
      position: absolute;
      inset: 0;
      display: block;
      border: 1px solid red;
      border-radius: 50%;
    }
    
    .icon::after {
      content: '';
      position: absolute;
      inset: 0;
      display: block;
      background: red;
      border-radius: 50%;
      clip-path: inset(0 calc(100% - var(--width, 0%)) 0 0);
    }
    <span class="icon" style="--width:10%"></span>
    <span class="icon" style="--width:20%"></span>
    <span class="icon" style="--width:30%"></span>
    <span class="icon" style="--width:40%"></span>
    <span class="icon" style="--width:50%"></span>
    <span class="icon" style="--width:60%"></span>
    <span class="icon" style="--width:70%"></span>
    <span class="icon" style="--width:80%"></span>
    <span class="icon" style="--width:90%"></span>

    Edit: More explanation

    The clip-path CSS property is a way to visually limit an element to a specific region, sort of like a stencil. It's very similar to the <clipPath> element in SVG, but since it's an CSS property, it can access CSS variables (aka custom properties). So I also combine it with --width (a custom property, you can name it whatever you want) to dynamically construct a clip rectangle based on the desired width.

    inset() means distant from 4 edges of its original box. It has similar syntax to other box-related property, so the calc(100% - var(--width, 0%)) means distant from right edge equals 100% minus --width (if --width is missing, default to 0%).

    Check the linked MDN page, they have more details and examples.