jackmiwamiwa devblog

フロントエンドをメインに行ったことをまとめていくサイトになります。

cssを使って画像を立体的に見せる方法

X(旧Twitter)で以下のものが流れてきて実装方法など気になったので、それを実装してみた内容になります。

実装を行ったもの

See the Pen hero-card by miwa_shuntaro (@miwashutaro0611) on CodePen.

<!-- @see https://codepen.io/t_afif/pen/mdzxJaa -->
<figure class="hero">
  <!--  NOTE: change image path  -->
  <!--  https://assets.codepen.io/1480814/necro.png  -->
  <img class="hero-image" src="https://placehold.jp/437x1000.png" alt="character image">
  <figcaption class="hero-figcaption">character1</figcaption>
</figure>

<figure class="hero">
  <img class="hero-image" src="https://placehold.jp/437x1000.png" alt="character image">
  <figcaption class="hero-figcaption">character2</figcaption>
</figure>

今回CSSについては「CSS nesting」を使用しているため、FireFoxでは動かない可能性があります。 (Mac & Chromeでは動作することを確認済みです。)

developer.mozilla.org

caniuse.com

/* @see https://codepen.io/t_afif/pen/mdzxJaa */
/* reset css */
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
}

body {
  width: 100%;
  min-height: 100vh;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(230px,35vmin);
  place-content: end center;
  gap: 50px;
}
/* content css */
:root {
  --time: .5s
}

.hero {
  width: 100%;
  aspect-ratio: 1;
  margin: 0 0 60px;
  padding: 5px 20px 0;
  display: grid;
  grid-template-rows: 100%;
  cursor: pointer;
  position: relative;
  filter: drop-shadow(0 0 20px rgb(0 0 0/50%));
  
  &::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 0;
    background: top/cover;
    transform-origin: bottom;
    filter: brightness(.9);
    transition: var(--time);
    background-image: url('https://placehold.jp/97e605/ffffff/680x980.png')
  }
  
  &:hover::before {
    filter: brightness(.3);
    transform: perspective(500px) rotateX(60deg);
  }
}

.hero-image {
  grid-area: 1/1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
  filter: contrast(.8) brightness(.7);
  place-self: end center;
  transition: var(--time);
  
  .hero:hover & {
     width: 130%;
    height: 255%;
    filter: contrast(1);
  }
}

.hero-figcaption {
  grid-area: 1/1;
  width: calc(100% + 40px);
  color: #fff;
  font-size: min(32px,5vmin);
  text-align: center;
  place-self: end center;
  transform: perspective(500px) translateY(100%) rotateX(-90deg);
  backface-visibility: hidden;
  transform-origin: top;
  background: #000;
  transition: var(--time);
  
  .hero:hover & {
    transform: perspective(500px)translateY(100%) rotateX(-30deg);
  }
}

制作手順

今回使用している画像としては以下になります。

1. HTMLの作成

キャラクター用の画像

https://placehold.jp/437x1000.png

キャラクターの背景にある画像

https://placehold.jp/97e605/ffffff/680x980.png

<figure class="hero">
  <!--  NOTE: change image path  -->
  <img class="hero-image" src="https://placehold.jp/437x1000.png" alt="character image">
  <figcaption class="hero-figcaption">character1</figcaption>
</figure>

こちらは通常の画像で使うHTMLと同じになります。 画像については、ホバー後の全体のものを使用したいため、縦長の画像を使用しています。 今回使用している画像サイズについては437x1000のものになります。

2. 背景画像部分の表示

  • 画像の縦横比を1:1に変更
  • 影を使用して立体感の演出
  • 画像を少しだけ暗くする

画像の縦横比を1:1に変更

要素に対して、aspect-ratioを設置することで横幅を設定するだけで高さも比率に合わせて調整してくれます。

developer.mozilla.org

影を使用して立体感の演出

画像の部分に影を適応させていきたいので、drop-shadowを使用しています。 (今回検証で制作したものについては透過されていない画像なので、box-shadowでも問題はないと思います。)

developer.mozilla.org

box-shadowとdrop-shadowの違いについては以下の記事が参考になります。

ics.media

画像を少しだけ暗くする

色味についても、少し落とした感じにしたいため、brightnessを使用しています。

developer.mozilla.org

今回のcssの内容について

.hero {
  width: 100%;
  aspect-ratio: 1;
  margin: 0 0 60px;
  padding: 5px 20px 0;
  display: grid;
  grid-template-rows: 100%;
  cursor: pointer;
  position: relative;
  filter: drop-shadow(0 0 20px rgb(0 0 0/50%));
  
  &::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 0;
    background: top/cover;
    transform-origin: bottom;
    filter: brightness(.9);
    transition: var(--time);
    background-image: url('https://placehold.jp/97e605/ffffff/680x980.png')
  }
}

3. imgタグで表示した画像の表示エリアの調整

  • gridのエリアに縦1・横1の割合で配置を行う
  • imgタグで配置した画像のサイズ・配置箇所の調整
  • コントラスト・明るさの調整

gridのエリアに縦1・横1の割合で配置を行う

親要素で指定したgridの要素に対してどの場所に配置を行うか指定を行う際に使用します。 今回の場合、親要素のgrid全体に対して表示を行うため、1/1のように記載しています。

developer.mozilla.org

imgタグで配置した画像のサイズ・配置箇所の調整

imgタグに対して、以下を使用することで

object-fit: コンテンツと画像の比率が合わない場合、全体を見えるように・横幅いっぱいなどの指定を行うことができます。 object-position: 画像の配置する場所を配置場所を指定することができます。

developer.mozilla.org

developer.mozilla.org

コントラストの調整

画像のコントラストを調整することができます。

developer.mozilla.org

今回のcssの内容について

.hero-image {
  grid-area: 1/1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
  filter: contrast(.8) brightness(.7);
  place-self: end center;
  transition: var(--time);
}

4. captionタグのスタイルの調整

  • gridのエリアに縦1・横1の割合で配置を行う(イメージと同じ箇所に配置を行う)
  • コンテンツの位置調整
  • 奥行きの設定
  • コンテンツの裏側を見せないようにする

gridのエリアに縦1・横1の割合で配置を行う(イメージと同じ箇所に配置を行う)

親要素で指定したgridの要素に対してどの場所に配置を行うか指定を行う際に使用します。 今回の場合、親要素のgrid全体に対して表示を行うため、1/1のように記載しています。

developer.mozilla.org

girdでの表示の場合、同じエリアに配置することでposition: absoluteと同じようなことを行うことができます。 詳細については以下の記事を参考にしてください。

coliss.com

コンテンツの位置調整

gridの中で要素をどの位置に表示を行うか指定することができます。

developer.mozilla.org

奥行きの設定

perspectiveを追加すると3次元での表現を行うことができます。

developer.mozilla.org

コンテンツの裏側を見せないようにする

要素が立体的な時にbackface-visibility: hidden;を指定することで裏側にある要素を隠すことができます。

developer.mozilla.org

今回のcssの内容について

.hero-figcaption {
  grid-area: 1/1;
  width: calc(100% + 40px);
  color: #fff;
  font-size: min(32px,5vmin);
  text-align: center;
  place-self: end center;
  transform: perspective(500px) translateY(100%) rotateX(-90deg);
  backface-visibility: hidden;
  transform-origin: top;
  background: #000;
  transition: var(--time);
}

5. 2から4で作成したものに対して、ホバーのインタラクションをつける

設定した各種各種内容について

hero全体で行っていることについて

背景画像用として作成して擬似要素の奥行きを追加し、要素の回転を行う

heroの画像で行っていることについて

コントラストを通常に戻して、元のサイズの画像の表示を行う

heroのキャプションで行っていることについて

位置を移動させて、要素を回転させる

今回のcssの内容について

.hero {
  &:hover::before {
    filter: brightness(.3);
    transform: perspective(500px) rotateX(60deg);
  }
}

.hero-image {
  .hero:hover & {
    width: 130%;
    height: 255%;
    filter: contrast(1);
  }
}

.hero-figcaption {
  .hero:hover & {
    transform: perspective(500px) translateY(100%) rotateX(-30deg);
  }
}