逆角丸デザインの実装 clip-pathによる逆角丸の実装とコンポーネント化

角丸と、逆に角丸を当てている要素からせり出したような角丸の2つを組み合わせて作られる、逆角丸デザインを実装する方法を解説します。また、逆角丸部分をコンポーネント化する際に考えるべきポイントについても紹介します。

発行

著者 中野 祐人 モダンフロントエンド・エンジニア
逆角丸デザインの実装 シリーズの記事一覧

逆角丸デザインとは

この記事では、逆角丸デザインについて、作り方やそれをWebフレームワーク上での開発でどのように管理すると使いやすいのかを解説します。

「逆角丸デザイン」……と言っても正式名称ではありません。今回の記事で解説する逆角丸デザインとは、通常の角丸と、角丸とは逆に丸く切り取られた角でえぐられたような形状の2つを組み合わせて作られるデザインのことを指します。次の画像のようになります。

日本語では「逆角丸デザイン」、英語では「Inverted Border Radius」と検索すると、このようなデザインについての情報が見つかります。

一見すると、特別なことはしていないようですが、画像の赤枠で囲った以下の部分が、簡単には実現できないことに気づくはずです。

  • 日付が表示されている部分の右上と左下
  • メッセージ(「明けまして……」)が表示されている部分の左上と右下

この赤枠で囲った部分が、日付とメッセージが入っている白地の部分の「逆角丸」部分です。ここは、写真から見れば「角丸」ですが、白地の部分から見れば「逆角丸」になっているわけです。このような逆角丸デザインを実現する方法を解説します。

どのように実装するのか

このデザインは、分解すると次の図のように、背景にある画像と前面にある要素、そしてその要素の角に逆角丸部分がある、という構成になっています。

このデザインを実現するには、この逆角丸部分を画像で作成したり、擬似要素で作成するなど、方法はいくつかあります。その中から、今回は筆者が実装してみて、一番良いと感じたclip-pathで逆角丸部分を作る方法を紹介したいと思います。

clip-pathは要素を指定したパスで切り取ることができるプロパティです。

記事執筆現在(2024年12月)では、今回利用するclip-pathの機能は主要ブラウザで対応されています。

逆に、IEなどのレガシーなブラウザには対応していないので、その場合は画像や擬似要素を使って逆角丸部分を作成する方法を使うほうが良いでしょう。

clip-pathプロパティについては、次のCodeGridの記事でも解説しているので参考にしてください。

逆角丸の他の実装方法との比較

今回の逆角丸を実現する方法はいくつかあり、たとえば、以下のような方法があります。

  1. 画像で逆角丸部分を作る
  2. 擬似要素で逆角丸部分を作る
  3. radial-gradient()で逆角丸部分を作る
  4. Data URLにしたSVGでマスクして、逆角丸部分を作る

1の画像で逆角丸部分を作る方法は、画像なので画像の読み込みが必要になるという点と、画像のサイズを気にしなければならない点が気になりました。

2の擬似要素で逆角丸を作る方法は画像ではないので上述した点を気にしなくてよいものの、擬似要素とbox-shadowなどを組み合わせた、一般的ではないコードになる点が気になりました。

3のradial-gradient()でも逆角丸を作ることができますが、この方法では逆角丸部分にジャギーが出て、滑らかな曲線になりませんでした。

4のData URLにしたSVGでマスクして逆角丸部分を作る方法は、悪くはないものの、逆角丸部分のSVGが必要な点でclip-pathよりは少し面倒かなと感じました。

このような他の案を踏まえて、最終的には今回紹介したclip-pathで作る方法が、それほどややこしくないし目立った欠点もなさそう、という結論に至りました。

clip-pathを使って逆角丸部分を作る

やること自体はシンプルで、clip-pathを使って要素を以下の画像のような逆角丸部分の形に切り抜くことで、逆角丸部分を作成しようというものです。

さっそく実装を紹介すると、次のようになります。

<span></span>
<style>
  span {
    width: 20px;
    display: block;
    aspect-ratio: 1;
    background-color: white;
    clip-path: polygon(
      0 100%,
      calc(sin(pi * 0.5 * 0.1) * 100%) calc(cos(pi * 0.5 * 0.1) * 100%),
      calc(sin(pi * 0.5 * 0.2) * 100%) calc(cos(pi * 0.5 * 0.2) * 100%),
      calc(sin(pi * 0.5 * 0.3) * 100%) calc(cos(pi * 0.5 * 0.3) * 100%),
      calc(sin(pi * 0.5 * 0.4) * 100%) calc(cos(pi * 0.5 * 0.4) * 100%),
      calc(sin(pi * 0.5 * 0.5) * 100%) calc(cos(pi * 0.5 * 0.5) * 100%),
      calc(sin(pi * 0.5 * 0.6) * 100%) calc(cos(pi * 0.5 * 0.6) * 100%),
      calc(sin(pi * 0.5 * 0.7) * 100%) calc(cos(pi * 0.5 * 0.7) * 100%),
      calc(sin(pi * 0.5 * 0.8) * 100%) calc(cos(pi * 0.5 * 0.8) * 100%),
      calc(sin(pi * 0.5 * 0.9) * 100%) calc(cos(pi * 0.5 * 0.9) * 100%),
      calc(sin(pi * 0.5 * 1) * 100%) calc(cos(pi * 0.5 * 1) * 100%),
      100% 0,
      100% 100%
    );
  }
</style>

aspect-ratio: 1で正方形を作り、clip-pathで逆角丸部分を切り抜くということ自体はシンプルですが、clip-path部分を見てわかるように、実際の実装では逆角丸部分の形を計算している部分が少し複雑になります。

clip-path部分では、polygon()関数を使って、逆角丸部分の形を指定しています。

polygon()関数は、カンマ区切りで時計回り、または反時計回りに座標を指定することで、任意の多角形を描画できます。指定しているパーセンテージはX座標が左端を起点、Y座標が上端を起点としたものです。

したがって、今回の指定は0% 100%という左下端から始まり、時計回りに、sin()cos()を使って計算した角丸を形成する座標、右上端、右下端という形になっています。

間の角丸部分は、sin()を使ってX座標を、cos()を使ってY座標を計算しています。

pi * 0.5とは、90度を表していて、それに、0.1, 0.2, 0.3……とかけることで、90度を10等分した円周上の点を10箇所指定しているということになります。図にすると次のような形です。

以上で、逆角丸部分自体は作成できました。

どのように使用するか

次に考えたいのは、この逆角丸部分をどのように使用できれば便利か、ということです。

Webフレームワークなどを用いた開発では、コンポーネントとして逆角丸部分を作成し、必要な箇所に配置するということができれば便利でしょう。ですので、コンポーネントとして実装する際に必要な変数を考えてみます。

逆角丸のサイズ

当たり前ですが、逆角丸のサイズを大きくすればより丸みを帯びたデザインに、小さくすればより角張ったデザインになります。

記事冒頭で作った逆角丸要素は横幅がそのまま半径になっているので、逆角丸と一緒に使われる角丸の数値と合わせると、統一感のあるデザインになります。逆に、逆角丸のサイズだけを変えるという使い方もできれば、デザインの幅が広がるので、変数として指定できるようにしておくと良いでしょう。

逆角丸の種類

次に、逆角丸の種類について考えます。

次の画像のように、逆角丸も通常の角丸と同じく、4つの角に応じた4種類があります。記事内では、これらをそれぞれ、通常の角丸と同じように、左上から時計回りに、top-lefttop-rightbottom-rightbottom-leftという名前で呼びます。

これについても、コンポーネントとして実装する際には、4種類を変数として指定するか、この種類に応じた4種類のコンポーネントを作るか、ということが考えられるでしょう。

逆角丸を置く場所

次に逆角丸を置く場所について考えます。冒頭の例の左上の日付セクションについて見ていくと、配置したい逆角丸の種類はbottom-rightです。このbottom-rightを配置できる場所は、以下の画像の赤枠で囲った、日付セクションの要素の下辺の左端と右辺の上端の2箇所で、それ以外の場所においてもデザイン上意味をなさないことがわかります。

同じように、メッセージが表示されている見出しのセクションに使われている逆角丸の種類はtop-rightです。これを配置できる場所は以下画像に示した、見出しセクションの要素の上辺の左端と右辺の下端の2箇所で、それ以外の場所では意味をなしません。

このデザインにおいて、逆角丸は背景と要素のつなぎ目をなくすために使われるので、このように逆角丸を配置する場所は、各種類ごとに2箇所しかありません。

さらに、上記の日付の例で使用しているbottom-rightの逆角丸は、日付の要素の下辺と右辺に配置され、上辺と左辺には使用できません。また、見出しの例で使用しているtop-rightの逆角丸が見出しの要素の上辺と右辺にしか配置されません。つまり、逆角丸を配置する2箇所とは、対象の要素の「上辺もしくは下辺のどこか」と「左辺もしくは右辺のどこか」の2つのパターンしかないこともわかります。

ですので、コンポーネントとして実装する際には、4種類の逆角丸それぞれにつき、配置箇所は2箇所で、さらに2箇所というのは「上下辺のどこか(縦)」と「左右辺のどこか(横)」なのかを指定できるようにするというのが良いでしょう。

実際の実装例

ここまでの考察を踏まえて、実際に筆者が作った逆角丸コンポーネントの実装例を紹介します。この実装はAstroで行われていますが、上述した概念を踏まえると、他のフレームワークでも同様の実装が可能です。

components/InvertedBorderRadius.astro

---
interface Props {
  // サイズ
  size: number;

  // 逆角丸の種類
  corner: "top-left" | "top-right" | "bottom-right" | "bottom-left";

  // 逆角丸を置く場所、上下辺のどこか(verctical)か、左右辺のどこか(horizontal)か
  position: "horizontal" | "vertical";
}
const { size, position, corner } = Astro.props;

let offset = {
  top: "",
  right: "",
  bottom: "",
  left: "",
};
let rotate = 0;

switch (corner) {
  case "top-left":
    rotate = 0;
    if (position === "horizontal") {
      offset.bottom = "0";
      offset.left = `-${size}px`;
    } else {
      offset.top = `-${size}px`;
      offset.right = "0";
    }
    break;
  case "top-right":
    rotate = 90;
    if (position === "horizontal") {
      offset.bottom = "0";
      offset.right = `-${size}px`;
    } else {
      offset.top = `-${size}px`;
      offset.left = "0";
    }
    break;
  case "bottom-right":
    rotate = 180;
    if (position === "horizontal") {
      offset.top = "0";
      offset.right = `-${size}px`;
    } else {
      offset.bottom = `-${size}px`;
      offset.left = "0";
    }
    break;
  case "bottom-left":
    rotate = 270;
    if (position === "horizontal") {
      offset.top = "0";
      offset.left = `-${size}px`;
    } else {
      offset.bottom = `-${size}px`;
      offset.right = "0";
    }
    break;
}

const style = {
  width: `${size}px`,
  ...offset,
  transform: `rotate(${rotate}deg)`,
};
---

<span style={style}></span>
<style>
  span {
    position: absolute;
    display: block;
    aspect-ratio: 1;
    clip-path: polygon(
      0% 100%,
      calc(sin(pi * 0.5 * 0.1) * 100%) calc(cos(pi * 0.5 * 0.1) * 100%),
      calc(sin(pi * 0.5 * 0.2) * 100%) calc(cos(pi * 0.5 * 0.2) * 100%),
      calc(sin(pi * 0.5 * 0.3) * 100%) calc(cos(pi * 0.5 * 0.3) * 100%),
      calc(sin(pi * 0.5 * 0.4) * 100%) calc(cos(pi * 0.5 * 0.4) * 100%),
      calc(sin(pi * 0.5 * 0.5) * 100%) calc(cos(pi * 0.5 * 0.5) * 100%),
      calc(sin(pi * 0.5 * 0.6) * 100%) calc(cos(pi * 0.5 * 0.6) * 100%),
      calc(sin(pi * 0.5 * 0.7) * 100%) calc(cos(pi * 0.5 * 0.7) * 100%),
      calc(sin(pi * 0.5 * 0.8) * 100%) calc(cos(pi * 0.5 * 0.8) * 100%),
      calc(sin(pi * 0.5 * 0.9) * 100%) calc(cos(pi * 0.5 * 0.9) * 100%),
      calc(sin(pi * 0.5 * 1) * 100%) calc(cos(pi * 0.5 * 1) * 100%),
      100% 0,
      100% 100%
    );
    background-color: var(--c-g0);
  }
</style>

このコンポーネントは、上述したサイズ、種類、置く場所をsizecornerpositionの3つのプロパティで受け取り、それに応じて逆角丸部分を作成しています。

switch文では、cornerpositionに応じて、逆角丸の位置と回転角度を決めています。

このように作成したInvertedBorderRadiusコンポーネントを以下のように使用することで、記事冒頭の画像のような逆角丸デザインを実現できます。

index.astro

 <body>
  <div class="container">
    <div class="date">
      <InvertedBorderRadius
        size={20}
        position="horizontal"
        corner="bottom-right"
      />
      <time datetime="2025-01-01">2025.01.01</time>
      <InvertedBorderRadius
        size={20}
        position="vertical"
        corner="bottom-right"
      />
    </div>
    <img
      src="https://images.unsplash.com/photo-1545569341-9eb8b30979d9?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    />
    <div class="text">
      <InvertedBorderRadius
        size={20}
        position="vertical"
        corner="top-right"
      />
      <p>明けまして、おめでとうございます🎉</p>
      <InvertedBorderRadius
        size={20}
        position="horizontal"
        corner="top-right"
      />
    </div>
  </div>
</body>
<style>
  .container {
    position: relative;
    display: flex;
    flex-direction: column;
  }
  .date {
    position: absolute;
    top: 0;
    background-color: white;
    padding: 8px 20px;
    border-bottom-right-radius: 20px;
  }
  img {
    border-radius: 20px;
  }
  .text {
    position: absolute;
    bottom: 0;
    background-color: white;
    padding: 0 20px;
    border-top-right-radius: 20px;
  }
</style>

まとめ

今回は逆角丸デザインを実装する方法を解説しました。

他のエンジニアからは、角丸の多いデザインは柔らかく見える効果がありそうな反面、実装コストとのバランスを考えたほうが良さそうという意見もありました。もし、このようなデザインを実装する機会があれば、ぜひ参考にしてみてください。