©︎ 2025 SATOSHI KAWASHIMA Portfolio.
採用サイト(自主制作)
- 概要
- figmaのデザインカンプからのコーディングをしました。
デザインを忠実に再現することや、どんな画面幅でも表示崩れがない構造を意識してリキッドレイアウトでコーディングしました。
保守しやすいようにCSSはBEMとFLOCSSを採用しています。
正しいHTMLのマークアップを意識しました。
セクションごとに違うアニメーションを実装し、デザインで指定された動きを忠実に再現することにこだわりました。
gulpを使用し、sassのコンパイル・jsやcssのファイルの圧縮・HTMLのフォーマット・画像のwebp化を全て自動化してスムーズに構築することを意識しました。
今まで写真編集でPhotoshopを触っていた経験を活かし、自身でモックアップ画像の作成も行いました。
主なアニメーション実装の3点を下記にてご覧いただければと思います。
1.無限に流れ続けるスライダー
2.文字がなぞるように出現
3.CTAでは特定の画面の表示領域に入ったら画像が扉のように開くアニメーションを実装。
*Basic認証*
user
$sample3104
Code Detail
1.無限に流れ続けるスライダー
FVに流れ続けるスライダーを採用することで最初に目をひく実装を目指しました。
HTML
<div class="p-mv__inner">
<div id="js-images-scroll" class="p-mv__images-scroll animated__fadeIn">
<div id="js-images" class="p-mv__images">
<picture class="p-mv__image1">
<source media="(min-width: 767px)" srcset="./assets/img/img-mv-1.webp">
<img src="./assets/img/sp/img-mv-sp-1.webp" alt="" width="269" height="560">
</picture>
<picture class="p-mv__image2">
<source media="(min-width: 767px)" srcset="./assets/img/img-mv-2.webp">
<img src="./assets/img/img-mv-2.webp" alt="" width="269" height="270">
</picture>
<picture class="p-mv__image3">
<source media="(min-width: 767px)" srcset="./assets/img/img-mv-3.webp">
<img src="./assets/img/img-mv-3.webp" alt="" width="269" height="270">
</picture>
<picture class="p-mv__image4">
<source media="(min-width: 767px)" srcset="./assets/img/img-mv-4.webp">
<img src="./assets/img/img-mv-4.webp" alt="" width="557" height="560">
</picture>
<picture class="p-mv__image5">
<source media="(min-width: 767px)" srcset="./assets/img/img-mv-5.webp">
<img src="./assets/img/img-mv-5.webp" alt="" width="269" height="270">
</picture>
<picture class="p-mv__image6">
<source media="(min-width: 767px)" srcset="./assets/img/img-mv-6.webp">
<img src="./assets/img/img-mv-6.webp" alt="" width="269" height="270">
</picture>
<picture class="p-mv__image7">
<source media="(min-width: 767px)" srcset="./assets/img/img-mv-7.webp">
<img src="./assets/img/img-mv-7.webp" alt="" width="557" height="560">
</picture>
</div>
</div>
CSS
.p-mv__images-scroll {
--images-width: 0px;
--image-gap: 20px;
width: fit-content;
display: flex;
gap: var(--image-gap);
animation: infinity-scroll-left 40s linear both infinite;
@include mq('md'){
--image-gap: 12px;
}
}
@keyframes infinity-scroll-left {
from {
translate: 0 0;
}
to {
translate: calc(-1 * (var(--images-width) + var(--image-gap))) 0;
}
}
.p-mv__images {
display: grid;
gap: var(--image-gap);
grid-template-columns: repeat(7, 268px);
grid-template-rows: repeat(2, 270px);
@include mq('md'){
grid-template-columns: repeat(7, 150px);
grid-template-rows: repeat(2, 254px);
}
img {
object-fit: cover;
width: 100%;
height: 100%;
}
}
.p-mv__image1 {
grid-column: 1 / 2;
grid-row: 1 / 3;
}
.p-mv__image2 {
grid-column: 2 / 3;
grid-row: 1 / 2;
}
.p-mv__image3 {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
.p-mv__image4 {
grid-column: 3 / 5;
grid-row: 1 / 3;
}
.p-mv__image5 {
grid-column: 5 / 6;
grid-row: 1 / 2;
}
.p-mv__image6 {
grid-column: 5 / 6;
grid-row: 2 / 3;
}
.p-mv__image7 {
grid-column: 6 / 8;
grid-row: 1 / 3;
}
JS
document.addEventListener("DOMContentLoaded", () => {
const imagesScroll = document.querySelector("#js-images-scroll"); // 無限アニメーション対象の要素を取得
const images = document.querySelector("#js-images"); // 無限アニメーションの1週分の要素を取得
const imagesWidth = images.scrollWidth; // 幅を取得
console.log('imagesWidth:', imagesWidth);
// itemsを複製
const imagesClone = images.cloneNode(true); // imagesを複製
imagesScroll.appendChild(imagesClone); // 複製した要素をimagesScrollに追加
imagesClone.classList.add("is-clone"); // is-cloneクラスを追加
imagesScroll.style.setProperty('--images-width', `${imagesWidth}px`); // CSS変数にセット
});
2.文字がなぞるように出現
文字がなぞるように出現するように実装することで、ワクワク感を演出することを目指しました。
HTML
<div class="p-message__text-wrapper">
<p class="text-flow p-message-text-flex__wrapper">
<span class="p-message__text-flex">
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 1s;">私たちは教育を通じて、</span>
<span class="text-flow__base" aria-hidden="true">私たちは教育を通じて、</span>
</span><br>
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 1.6s;">日本から世界へ羽ばたくビジネスパーソンを数多く育ててきました。</span>
<span class="text-flow__base" aria-hidden="true">日本から世界へ羽ばたくビジネスパーソンを数多く育ててきました。</span>
</span><br>
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 2.2s;">そしてこれからは、</span>
<span class="text-flow__base" aria-hidden="true">そしてこれからは、</span>
</span><br>
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 2.8s;">アジアをはじめとする海外拠点にも活動の場を広げ、<br class="sp-hidden">世界規模で人と人を結びつける存在を目指します。</span>
<span class="text-flow__base" aria-hidden="true">アジアをはじめとする海外拠点にも活動の場を広げ、<br class="sp-hidden">世界規模で人と人を結びつける存在を目指します。</span>
</span><br>
</span><!-- /.p-message__text-flex -->
<span class="p-message__text-flex">
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 3.3s;">あなたが持つ可能性は、まだ形になっていないかもしれません。</span>
<span class="text-flow__base" aria-hidden="true">あなたが持つ可能性は、まだ形になっていないかもしれません。</span>
</span><br>
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 3.9s;">しかし、挑戦する意思さえあれば、</span>
<span class="text-flow__base" aria-hidden="true">しかし、挑戦する意思さえあれば、</span>
</span><br>
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 4.5s;">その力は必ず世界を変える原動力になります。</span>
<span class="text-flow__base" aria-hidden="true">その力は必ず世界を変える原動力になります。</span>
</span><br>
</span>
<span class="p-message__text-flex">
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 5.1s;">私たちは、挑戦する人を歓迎します。</span>
<span class="text-flow__base" aria-hidden="true">私たちは、挑戦する人を歓迎します。</span>
</span><br>
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 5.7s;">あなたの挑戦が、やがて世界の新しい価値をつくる。</span>
<span class="text-flow__base" aria-hidden="true">あなたの挑戦が、やがて世界の新しい価値をつくる。</span>
</span><br>
<span class="text-flow__line">
<span class="text-flow__cover" style="--delay: 6.3s;">その未来を、私たちと共に描いていきませんか。</span>
<span class="text-flow__base" aria-hidden="true">その未来を、私たちと共に描いていきませんか。</span>
</span><br>
</span>
</p><!-- /.text-flow -->
</div><!-- /.p-message__text-wrapper -->
CSS
.text-flow {
width: fit-content;
}
.text-flow__line {
position: relative;
display: inline-block;
font-size: rem(18);
line-height: 2.2; /* 39.6px */
@include mq('md'){
font-size: rem(15);
}
}
@keyframes text-flow {
from {
clip-path: inset(0 100% 0 0);
}
to {
clip-path: inset(0 0 0 0);
}
}
.text-flow__cover {
--delay: 1s;
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 2;
color: $border-color-white;
// 初期状態は非表示(clip-pathで隠しておく)
clip-path: inset(0 100% 0 0);
// 親要素に .is-active がついたらアニメーション開始
.is-active & {
animation: text-flow 0.8s cubic-bezier(0.19, 1, 0.22, 1) var(--delay) both;
}
}
.text-flow__base {
display: block;
position: relative;
color: #023E78;
}
JS
document.addEventListener('DOMContentLoaded', () => {
const options = {
root: null, // ビューポートを基準にする
rootMargin: '0px',
threshold: 0.2 // 要素が20%見えたら実行
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 画面に入ったら 'is-active' クラスを付与
entry.target.classList.add('is-active');
// 一度発火したら監視を解除する場合
observer.unobserve(entry.target);
}
});
}, options);
// 監視対象の要素(メッセージ全体を包む要素など)
const target = document.querySelector('.p-message__text-wrapper');
if (target) {
observer.observe(target);
}
});
3.CTAでは特定の画面の表示領域に入ったら画像が扉のように開くアニメーションを実装。
扉が開くアニメーションを実装することで「歓迎されている・驚き」を印象付けることを目指しました。
HTML
<section class="p-join">
<div class="content">
<div class="p-join-text__wrapper">
<h2 class="p-join-text__title c-text-large">Join <br>our company!</h2>
<p class="p-join-text__description">
私たちは、挑戦する人を歓迎します。<br>
あなたの挑戦が、やがて世界の新しい価値をつくる。<br>
その未来を、私たちと共に描いていきませんか。
</p>
<div class="c-btn p-join__btn">
<a href="" class="c-btn__entry p-join__btn-entry">ENTRY</a><!-- /.c-gnav__button-dl is-white -->
<span class="c-btn__circle p-join__btn-circle">
<svg xmlns="http://www.w3.org/2000/svg" width="6" height="6" viewBox="0 0 6 6" fill="none">
<circle cx="3" cy="3" r="3" fill="#FFF400"/>
</svg>
</span>
</div><!-- /.c-btn -->
</div><!-- /.p-join-text__wrapper -->
<div class="door-left">
<img src="./assets/img/img-join-left.webp" alt="笑顔で面接を受けている爽やかな男性の画像" width="688" height="636" >
</div>
<div class="door-right">
<img src="./assets/img/img-join-right.webp" alt="笑顔で面接を行っている綺麗な女性の画像" width="688" height="636">
</div>
</div>
</section>
CSS
.door-left,
.door-right {
position: absolute;
top: 0;
width: 50%;
height: 100%;
overflow: hidden;
transition: transform 0.5s;
transition-timing-function: ease-out;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
@include mq('md'){
display: none;
}
img {
width: 100%;
object-fit: cover;
display: block;
}
}
.door-left {
left: 0;
transform-origin: left center;
}
.door-right {
right: 0;
transform-origin: right center;
}
/* スクロール時のアニメーション */
.content.is-open .door-left {
transform: rotateY(62deg);
}
.content.is-open .door-right {
transform: rotateY(-62deg);
}
JS
document.addEventListener('DOMContentLoaded', () => {
const target = document.querySelector('.content'); // 監視対象
const observerOptions = {
root: null, // ビューポートを基準にする
rootMargin: '0px',
threshold: 0.6 // 50%が見えたら発火(数値はお好みで0.1〜1.0)
};
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 画面内に入ったらクラスを付与
entry.target.classList.add('is-open');
// 一度発火したら監視を止める場合
// observer.unobserve(entry.target);
} else {
//スクロールに応じて何度も発火
entry.target.classList.remove('is-open');
}
});
}, observerOptions);
if (target) {
observer.observe(target);
}
});