CSSを用いたモダンな視覚効果の実装

現代のウェブデザインにおいて、CSSは単なるレイアウトツールに留まらず、ユーザーインターフェースに深みとインタラクティブ性をもたらす多様な視覚効果を実装するために不可欠な要素です。本記事では、box-shadow、text-shadow、border-radius、background、clip-path、そしてtransformといった主要なCSSプロパティを活用し、魅力的なデザインを実現するためのテクニックを解説します。

ボックスシャドウ (box-shadow)

box-shadowプロパティは、要素に影を適用することで立体感や奥行きを与えることができます。このプロパティは、水平方向のオフセット、垂直方向のオフセット、ぼかし半径、広がり半径、色、そして影の種類(insetで内側、省略で外側)を指定します。

基本的な影の適用 (外側)

要素の周りに典型的なドロップシャドウを作成し、浮き上がったような視覚効果を表現します。

<style>
  .outer-shadow-example {
    width: 80px;
    height: 80px;
    background-color: #f3f3f3;
    box-shadow: 6px 6px 12px rgba(0, 0, 0, 0.4);
    border-radius: 5px;
    display: grid;
    place-items: center;
    font-family: sans-serif;
    color: #333;
    font-size: 0.9em;
  }
</style>
<div class="outer-shadow-example">外側影</div>

内側の影 (Inset Shadow)

insetキーワードを使用することで、要素の内側に影を作り出し、窪んだような視覚効果を表現できます。

<style>
  .inner-shadow-example {
    width: 80px;
    height: 80px;
    background-color: #e0e0e0;
    box-shadow: inset 5px 5px 10px rgba(0, 0, 0, 0.6);
    border-radius: 5px;
    display: grid;
    place-items: center;
    font-family: sans-serif;
    color: #555;
    font-size: 0.9em;
  }
</style>
<div class="inner-shadow-example">内側影</div>

幅のないボーダーの表現

box-shadowの広がり半径 (spread-radius) を利用することで、要素自体にborderプロパティを使用せずに、あたかもボーダーが適用されているかのような視覚効果を作り出すことができます。

<style>
  .border-like-shadow {
    width: 70px;
    height: 70px;
    background-color: #ff9900;
    box-shadow: 0 0 0 7px #007bff; /* offset-x, offset-y, blur-radius, spread-radius */
    border-radius: 3px;
    display: grid;
    place-items: center;
    font-family: sans-serif;
    color: white;
    font-size: 0.9em;
  }
</style>
<div class="border-like-shadow">擬似ボーダー</div>

要素の複製効果

複数のbox-shadow値をカンマで区切って指定することで、元の要素を複製したかのような視覚効果を生成できます。これは、複雑な背景パターンや装飾的な要素を作成する際に役立ちます。

<style>
  .duplicated-elements {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background-color: #e74c3c;
    margin: 20px;
    box-shadow: 25px 0 0 0 #3498db, -25px 0 0 0 #27ae60;
  }
</style>
<div class="duplicated-elements"></div>

box-shadowの主な利用目的は、要素に奥行きを与える、装飾的なボーダーを作成する、あるいは複雑なグラフィックを単一の要素で表現することです。

テキストシャドウ (text-shadow)

text-shadowプロパティは、テキストに影を適用し、立体感や視覚的な強調効果をもたらします。これもbox-shadowと同様に、影のオフセット、ぼかし半径、色を指定します。

テキストへの影の適用

テキストを背景から浮き上がらせる、あるいはネオン効果のような特別なスタイルを与えることができます。

<style>
  .text-shadow-example {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-size: 3em;
    color: #333;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
    margin: 20px;
  }
  .neon-text {
    font-family: 'Arial Black', sans-serif;
    font-size: 3.5em;
    color: #fff;
    text-shadow: 0 0 5px #00f, 0 0 10px #00f, 0 0 20px #00f, 0 0 40px #0f0, 0 0 80px #0f0;
    background-color: #000;
    padding: 10px;
    display: inline-block;
    margin: 20px;
  }
</style>
<p class="text-shadow-example">立体的なテキスト</p>
<p class="neon-text">NEON</p>

ボーダーラディウス (border-radius)

border-radiusプロパティは、要素の角を丸めるために使用されます。単一の値で全ての角を均一に丸めることも、複数の値で各角の丸みを個別に設定することも可能です。これにより、円、半円、扇形、またはより複雑なカスタムシェイプを作成できます。

角丸矩形

最も一般的な使用例で、角を滑らかにします。

<style>
  .rounded-rectangle {
    width: 90px;
    height: 50px;
    background-color: #2ecc71;
    border-radius: 10px;
    margin: 20px;
    display: grid;
    place-items: center;
    color: white;
    font-family: sans-serif;
  }
</style>
<div class="rounded-rectangle">角丸</div>

半円または扇形

特定の角に大きなborder-radiusを適用することで、半円や扇形のような形状を作成できます。

<style>
  .half-circle {
    width: 100px;
    height: 50px; /* 高さ widthの半分 */
    background-color: #f1c40f;
    border-radius: 50px 50px 0 0; /* 上の角を丸く */
    margin: 20px;
    display: grid;
    place-items: center;
    color: white;
    font-family: sans-serif;
  }
  .quarter-circle {
    width: 50px;
    height: 50px;
    background-color: #e67e22;
    border-bottom-left-radius: 50px; /* 左下の角のみ丸く */
    margin: 20px;
  }
</style>
<div class="half-circle">半円</div>
<div class="quarter-circle"></div>

特定のコーナーデザイン

border-bottom-left-radiusなどの個別プロパティを使用して、ユニークなコーナーデザインを実現できます。

<style>
  .custom-corner {
    width: 70px;
    height: 70px;
    background-color: #ecf0f1;
    border-bottom: 2px solid #34495e;
    border-radius: 0; /* まずリセット */
    border-bottom-left-radius: 15px;
    border-bottom-right-radius: 15px;
    margin: 20px;
    display: grid;
    place-items: center;
    color: #34495e;
    font-family: sans-serif;
    font-size: 0.8em;
  }
</style>
<div class="custom-corner">特定の角</div>

背景 (background)

backgroundプロパティは、要素の背景を詳細に制御します。単色の設定から、画像、グラデーション、そして複雑なパターンまで、多岐にわたる表現が可能です。

CSSグラデーションとパターン

linear-gradientやradial-gradientを組み合わせることで、画像ファイルを使わずに複雑な幾何学模様やテクスチャを生成できます。

<style>
  .striped-background {
    width: 300px;
    height: 60px;
    background:
      linear-gradient(45deg, #f0f0f0 25%, transparent 25%, transparent 75%, #f0f0f0 75%, #f0f0f0),
      linear-gradient(135deg, #f0f0f0 25%, transparent 25%, transparent 75%, #f0f0f0 75%, #f0f0f0);
    background-size: 20px 20px;
    margin: 20px;
  }

  .checkerboard-background {
    width: 300px;
    height: 60px;
    background-image:
      linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
      linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
    background-position: 0 0, 15px 15px; /* 市松模様の効果に合わせて調整 */
    background-size: 30px 30px;
    margin: 20px;
  }
</style>
<div class="striped-background"></div>
<div class="checkerboard-background"></div>

スプライトアニメーション

background-positionとtransitionプロパティを組み合わせることで、CSSスプライトを使用した滑らかなアニメーションを実現できます。これは、複数のフレームを持つ画像を一枚にまとめ、必要な部分だけを表示を切り替える手法です。

<style>
  /* 仮のスプライト画像 (20px x 40px, 上半分が通常状態、下半分がホバー状態) */
  .sprite-icon {
    width: 20px;
    height: 20px;
    background-image: url('https://via.placeholder.com/20x40/8e44ad/ffffff?text='); /* プレースホルダー画像 */
    background-repeat: no-repeat;
    background-position: 0 0; /* 通常状態は上半分 */
    transition: background-position 0.3s ease-in-out;
    margin: 20px;
  }
  .sprite-icon:hover {
    background-position: 0 -20px; /* ホバー時は下半分に移動 */
  }
</style>
<div class="sprite-icon"></div>

背景画像のサイズ調整

background-sizeプロパティは、背景画像の表示方法を制御します。coverは要素全体を覆うように画像を拡大縮小し、containは画像全体が要素内に収まるように調整します。

<style>
  .bg-sizer {
    width: 200px;
    height: 120px;
    border: 1px solid #ccc;
    background-image: url('https://picsum.photos/id/237/200/120'); /* プレースホルダー画像 */
    background-repeat: no-repeat;
    margin: 20px;
    font-family: sans-serif;
    color: white;
    text-shadow: 1px 1px 2px black;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .bg-sizer.cover {
    background-size: cover;
  }
  .bg-sizer.contain {
    background-size: contain;
  }
</style>
<div class="bg-sizer cover">Cover</div>
<div class="bg-sizer contain">Contain</div>

クリップパス (clip-path)

clip-pathプロパティを使用すると、要素を任意の形状に切り抜くことができます。これにより、矩形以外の複雑な形状のUI要素を作成することが可能になります。polygon(), circle(), ellipse(), inset()などの関数が利用でき、カスタムSVGパスを適用することも可能です。

多角形による切り抜き

polygon()関数を用いて、頂点の座標を指定することで、多角形に要素をクリップできます。

<style>
  .clipped-shape-container {
    width: 250px;
    height: 150px;
    background-image: url('https://picsum.photos/id/1040/250/150'); /* プレースホルダー画像 */
    background-repeat: no-repeat;
    background-size: cover;
    float: left;
    margin: 15px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: sans-serif;
    color: white;
    text-shadow: 1px 1px 2px black;
  }
  .clipped-shape-polygon {
    clip-path: polygon(50% 0%, 90% 25%, 90% 75%, 50% 100%, 10% 75%, 10% 25%); /* 六角形 */
  }
</style>
<div class="clipped-shape-container">元の画像</div>
<div class="clipped-shape-container clipped-shape-polygon">クリップ済</div>

clip-pathは、画像の装飾や特殊なボタン形状など、幅広い用途で利用されます。

3D変換 (CSS Transforms)

CSSのtransformプロパティは、要素を2Dまたは3D空間で移動、回転、拡大縮小、傾斜させることができます。3D効果を適用するには、transform-style: preserve-3dとperspectiveプロパティが重要になります。

  • transform-style: preserve-3d: 親要素の子要素が3D空間内に配置されるように指定します。
  • perspective: 3D要素に遠近感を与え、視点からの距離を定義します。値が小さいほど強い遠近感が生まれます。
  • perspective-origin: 視点の位置を設定します。

フリップカード効果

2つの面を持つカードが、ホバー時に3Dで反転する効果を実装します。

<style>
  .flip-card-wrapper {
    width: 150px;
    height: 100px;
    border: 1px solid #ddd;
    position: relative;
    margin: 30px;
    perspective: 600px; /* 親要素に遠近感を与える */
    font-family: sans-serif;
    text-align: center;
    line-height: 100px;
    font-size: 1.1em;
  }
  .flip-card-inner {
    width: 100%;
    height: 100%;
    position: absolute;
    transform-style: preserve-3d; /* 子要素を3D空間に保持 */
    transition: transform 0.6s;
    transform-origin: center right; /* 右端を軸に回転 */
  }
  .flip-card-wrapper:hover .flip-card-inner {
    transform: rotateY(-180deg);
  }
  .flip-card-face {
    position: absolute;
    width: 100%;
    height: 100%;
    backface-visibility: hidden; /* 裏面を非表示に */
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .flip-card-front {
    background-color: #3498db;
    transform: rotateY(0deg);
  }
  .flip-card-back {
    background-color: #e74c3c;
    transform: rotateY(180deg);
  }
</style>
<div class="flip-card-wrapper">
  <div class="flip-card-inner">
    <div class="flip-card-face flip-card-front">
      フロント
    </div>
    <div class="flip-card-face flip-card-back">
      バック
    </div>
  </div>
</div>

3Dカルーセル

複数の画像が3D空間で回転するようなカルーセル効果を実装します。これはtransform-style: preserve-3dとrotateXを組み合わせた高度な例です。

<style>
  .carousel-viewport {
    width: 600px;
    height: 337px;
    overflow: hidden;
    margin: 20px auto;
    border: 1px solid #eee;
    background-color: #f8f8f8;
  }
  .carousel-stage {
    position: relative;
    width: 100%;
    height: 100%;
    perspective: 1000px; /* 遠近感 */
    transform-style: preserve-3d;
  }
  .carousel-column {
    position: absolute;
    height: 100%;
    width: 100px; /* 600px / 6 columns */
    transform-style: preserve-3d;
    transform-origin: 50% 50% 0; /* 中心を軸に */
    transition: transform 1s ease-in-out;
    transform: translateZ(-290px); /* 遠ざけて配置 */
    left: var(--column-offset); /* CSS変数で位置指定 */
  }
  .carousel-stage:hover .carousel-column {
    transform: translateZ(-290px) rotateX(60deg); /* ホバーで回転 */
  }
  .carousel-panel {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    background-repeat: no-repeat;
    background-size: cover; /* 画像をカバー */
    backface-visibility: hidden; /* 裏面を非表示 */
  }
  /* 各パネルの回転と奥行きの調整 */
  .panel-1 { transform: rotateX(0deg) translateZ(290px); }
  .panel-2 { transform: rotateX(-60deg) translateZ(290px); }
  .panel-3 { transform: rotateX(-120deg) translateZ(290px); }
  .panel-4 { transform: rotateX(-180deg) translateZ(290px); }
  .panel-5 { transform: rotateX(-240deg) translateZ(290px); }
  .panel-6 { transform: rotateX(-300deg) translateZ(290px); }

  .indicator-nav {
    width: 600px;
    text-align: center;
    margin-top: 15px;
  }
  .indicator-nav .dot-button {
    display: inline-block;
    background-color: rgba(0, 0, 0, 0.3);
    width: 15px;
    height: 15px;
    border-radius: 50%;
    margin: 0 4px;
    cursor: pointer;
    transition: background-color 0.3s;
  }
  .indicator-nav .dot-button.is-active {
    background-color: rgba(0, 0, 0, 0.8);
  }
</style>
<div class="carousel-viewport">
  <div class="carousel-stage">
    <div class="carousel-column" style="--column-offset: 0px; transition-delay: 0s;">
      <div class="carousel-panel panel-1" style="background-image: url(https://picsum.photos/600/337?random=1); background-position: 0 0;"></div>
      <div class="carousel-panel panel-2" style="background-image: url(https://picsum.photos/600/337?random=2); background-position: 0 0;"></div>
      <div class="carousel-panel panel-3" style="background-image: url(https://picsum.photos/600/337?random=3); background-position: 0 0;"></div>
      <div class="carousel-panel panel-4" style="background-image: url(https://picsum.photos/600/337?random=4); background-position: 0 0;"></div>
      <div class="carousel-panel panel-5" style="background-image: url(https://picsum.photos/600/337?random=5); background-position: 0 0;"></div>
      <div class="carousel-panel panel-6" style="background-image: url(https://picsum.photos/600/337?random=6); background-position: 0 0;"></div>
    </div>
    <div class="carousel-column" style="--column-offset: 100px; transition-delay: 0.1s;">
      <div class="carousel-panel panel-1" style="background-image: url(https://picsum.photos/600/337?random=1); background-position: -100px 0;"></div>
      <div class="carousel-panel panel-2" style="background-image: url(https://picsum.photos/600/337?random=2); background-position: -100px 0;"></div>
      <div class="carousel-panel panel-3" style="background-image: url(https://picsum.photos/600/337?random=3); background-position: -200px 0;"></div>
      <div class="carousel-panel panel-4" style="background-image: url(https://picsum.photos/600/337?random=4); background-position: -300px 0;"></div>
      <div class="carousel-panel panel-5" style="background-image: url(https://picsum.photos/600/337?random=5); background-position: -400px 0;"></div>
      <div class="carousel-panel panel-6" style="background-image: url(https://picsum.photos/600/337?random=6); background-position: -500px 0;"></div>
    </div>
    <div class="carousel-column" style="--column-offset: 200px; transition-delay: 0.2s;">
      <div class="carousel-panel panel-1" style="background-image: url(https://picsum.photos/600/337?random=1); background-position: -200px 0;"></div>
      <div class="carousel-panel panel-2" style="background-image: url(https://picsum.photos/600/337?random=2); background-position: -200px 0;"></div>
      <div class="carousel-panel panel-3" style="background-image: url(https://picsum.photos/600/337?random=3); background-position: -200px 0;"></div>
      <div class="carousel-panel panel-4" style="background-image: url(https://picsum.photos/600/337?random=4); background-position: -200px 0;"></div>
      <div class="carousel-panel panel-5" style="background-image: url(https://picsum.photos/600/337?random=5); background-position: -200px 0;"></div>
      <div class="carousel-panel panel-6" style="background-image: url(https://picsum.photos/600/337?random=6); background-position: -200px 0;"></div>
    </div>
    <div class="carousel-column" style="--column-offset: 300px; transition-delay: 0.3s;">
      <div class="carousel-panel panel-1" style="background-image: url(https://picsum.photos/600/337?random=1); background-position: -300px 0;"></div>
      <div class="carousel-panel panel-2" style="background-image: url(https://picsum.photos/600/337?random=2); background-position: -300px 0;"></div>
      <div class="carousel-panel panel-3" style="background-image: url(https://picsum.photos/600/337?random=3); background-position: -300px 0;"></div>
      <div class="carousel-panel panel-4" style="background-image: url(https://picsum.photos/600/337?random=4); background-position: -300px 0;"></div>
      <div class="carousel-panel panel-5" style="background-image: url(https://picsum.photos/600/337?random=5); background-position: -300px 0;"></div>
      <div class="carousel-panel panel-6" style="background-image: url(https://picsum.photos/600/337?random=6); background-position: -300px 0;"></div>
    </div>
    <div class="carousel-column" style="--column-offset: 400px; transition-delay: 0.4s;">
      <div class="carousel-panel panel-1" style="background-image: url(https://picsum.photos/600/337?random=1); background-position: -400px 0;"></div>
      <div class="carousel-panel panel-2" style="background-image: url(https://picsum.photos/600/337?random=2); background-position: -400px 0;"></div>
      <div class="carousel-panel panel-3" style="background-image: url(https://picsum.photos/600/337?random=3); background-position: -400px 0;"></div>
      <div class="carousel-panel panel-4" style="background-image: url(https://picsum.photos/600/337?random=4); background-position: -400px 0;"></div>
      <div class="carousel-panel panel-5" style="background-image: url(https://picsum.photos/600/337?random=5); background-position: -400px 0;"></div>
      <div class="carousel-panel panel-6" style="background-image: url(https://picsum.photos/600/337?random=6); background-position: -400px 0;"></div>
    </div>
    <div class="carousel-column" style="--column-offset: 500px; transition-delay: 0.5s;">
      <div class="carousel-panel panel-1" style="background-image: url(https://picsum.photos/600/337?random=1); background-position: -500px 0;"></div>
      <div class="carousel-panel panel-2" style="background-image: url(https://picsum.photos/600/337?random=2); background-position: -500px 0;"></div>
      <div class="carousel-panel panel-3" style="background-image: url(https://picsum.photos/600/337?random=3); background-position: -500px 0;"></div>
      <div class="carousel-panel panel-4" style="background-image: url(https://picsum.photos/600/337?random=4); background-position: -500px 0;"></div>
      <div class="carousel-panel panel-5" style="background-image: url(https://picsum.photos/600/337?random=5); background-position: -500px 0;"></div>
      <div class="carousel-panel panel-6" style="background-image: url(https://picsum.photos/600/337?random=6); background-position: -500px 0;"></div>
    </div>
  </div>
</div>
<div id="carouselIndicators" class="indicator-nav">
  <span class="dot-button is-active" data-index="0"></span>
  <span class="dot-button" data-index="1"></span>
  <span class="dot-button" data-index="2"></span>
  <span class="dot-button" data-index="3"></span>
  <span class="dot-button" data-index="4"></span>
  <span class="dot-button" data-index="5"></span>
</div>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const indicatorContainer = document.getElementById('carouselIndicators');
    const dotButtons = indicatorContainer.querySelectorAll('.dot-button');
    const carouselColumns = document.querySelectorAll('.carousel-column');

    indicatorContainer.addEventListener('click', (event) => {
      const clickedDot = event.target.closest('.dot-button');
      if (!clickedDot) return;

      dotButtons.forEach(dot => dot.classList.remove('is-active'));
      clickedDot.classList.add('is-active');

      const activeIndex = parseInt(clickedDot.dataset.index, 10);
      carouselColumns.forEach((column, colIndex) => {
        const delay = colIndex * 0.1; // 0.1s delay for each column
        column.style.transitionDelay = `${delay}s`;
        column.style.transform = `translateZ(-290px) rotateX(${60 * activeIndex}deg)`;
      });
    });
  });
</script>

タグ: CSS Webデザイン UI/UX トランスフォーム アニメーション

10月11日 20:09 投稿