はじめに
お疲れ様です。 DMM WEBCAMP Advent Calendar 2020の4日目を担当させていただきます。 メンターの@koseiinfratopです。 みなさん先日OAされたFNS歌謡祭はみましたか?
バンタンや3代目JSBなど豪華アーティストが出演されていて本当におもしろい番組でしたね。なかでもユーミンと嵐のコラボは感動的でした。
OA当日僕はふと思いました。嵐はいつごろ出番なのだろうかと。
そこでFNS歌謡祭の公式HPを訪れたことで今回のAdvent Calendarで何を書くがが決定しました。
一度公式HPを訪れていただけるとわかるのですが、アーティストの画像にカーソルを乗せてみてください。カーソルを乗せると薄黒いボックスとアーティスト名が出てきます。僕は疑問に思いました。これはどうやって実装しているのかと。。。 ということで今回は疑問に思ったことを解消するためにFNSのHPのようにカーソルを画像に載せるとアニメーションが発火するような機能を実装しようと思います。> - *開発効率を上げたかったため今回はホットリロードが可能なReact.jsでコーディングしました。* > - *しれっとiTunesAPIも使用しています。*
1.html(js)を記述
music.js
...*iTunesAPI関係の処理は割愛
return(
<div className="searchresult">
{artistData.map(artistdata => (
<ArtistData
key={artistdata.CollectionId.toString()}
id={artistdata.CollectionId}
name={artistdata.ArtistName}
album={artistdata.AlbumName}
albumUrl={artistdata.AlbumUrl}
genre={artistdata.AlbumGenre}
release={artistdata.AlbumRelease}
/>
)
)}
</div>
);
artistdata.js
import React from 'react';
const ArtistData = (props) => {
return(
<div className={props.id ? 'album': 'noalbum'}>
<div className="flex">
<img src={props.albumUrl} alt={props.album} className="albumImage"/>
<div>
<p>ジャンル: <b>{props.genre}</b></p>
</div>
</div>
<div className="mask">
<div className="caption">{props.album}</div>
</div>
<div>
<p>アーティスト: <b>{props.name}</b></p>
<p>アルバム名: <b>{props.album}</b></p>
<p>リリース日: <b>{props.release}</b></p>
</div>
</div>
);
}
export default ArtistData
2.CSSの記述
*ポイントだけコメントアウトを用いて解説します。music.css
.album {
width: 300px;
height: 230px;
/* overflow・・・アルバムクラスのdivタグ範囲内に内容が収まらない場合(今回で言うとmaskクラスのdivタグがはみ出る)の処理
overflow: hidden; /* 表示させないようにしている */
margin: 10px 8px 10px 16px;
position: relative;
border: ridge 10px #87CEFA;
}
.mask {
width: 100%;
height: 100%;
position: absolute;
top: -100%; /* 枠の上に配置し非表示にする。 */
opacity: 0; /* マスクスクラス内を透明化(0)にすることで非表示にする。*/
background-color: rgba(0,0,0,0.4);
transition: all 0.6s ease;
}
.caption {
font-size: 130%;
text-align: center;
color: #fff;
}
img.alabumImage {
width: 80%;
height: 80%;
position: absolute;
}
.album:hover .mask { アルバムクラス内をhover(カーソルを乗せる)時に発火する。
opacity: 1; /* マスクを完全に不透明表示にする */
padding-top: 80px; /* ホバーで下にずらす */
top: 0; /* 先ほどのtop: -100% から top: 0;にすることにより下から降りてくるように見せることができる */
}
デモンストレーション
![qiita_article.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541740/39921073-9ea3-585c-104e-ec92768214fc.gif)詳細な動き
form内にアーティスト(曲名も可)を入力し検索ボタンをクリックすることでitunesapiからアーティストが発売したアルバムを取得し、表示しています。music.js
import React, {useState } from 'react';
import axios from 'axios';
import ArtistData from './artistdata.js';
import '../styles/music.css'
const Music = () => {
const [artist, setArtist] = useState('');
const [artistData, setArtistData] = useState([{
CollectionId: '',
ArtistName: '',
AlbumName: '',
AlbumUrl: '',
AlbumGenre: '',
AlbumRelease: '',
}]);
async function itunesGet(params){
try{
const prm = params.trim();
const response = await axios.get(`https://itunes.apple.com/search?term=${prm}&entity=album`)
const responsedata = response.data.results
const responseAPI = responsedata.map(value => {
return {
CollectionId: value.collectionId,
ArtistName: value.artistName,
AlbumName: value.collectionName,
AlbumUrl: value.artworkUrl100,
AlbumGenre: value.primaryGenreName,
AlbumRelease: value.releaseDate,
};
}
);
setArtistData(responseAPI);
console.log(artistData);
}catch(error) {
const {
status, statusText
} = error.response;
console.log(`Error! HTTP Status: ${status} ${statusText}`)
}
};
return (
<div>
<form
onSubmit = {e => {
e.preventDefault();
const artistnameElement = e.target.elements["artist"];
console.log(artistnameElement.value);
itunesGet(artistnameElement.value);
setArtist(artistnameElement.value);
artistnameElement.value = '';
}}
>
<input type="text" id="artist"
placeholder="アーティスト名または曲名を入力してください"
/>
<button type="submit">検索する</button>
</form>
<p className="result">検索結果: <b>{artist}</b></p>
<div className="searchresult">
{artistData.map(artistdata => (
<ArtistData
key={artistdata.CollectionId.toString()}
id={artistdata.CollectionId}
name={artistdata.ArtistName}
album={artistdata.AlbumName}
albumUrl={artistdata.AlbumUrl}
genre={artistdata.AlbumGenre}
release={artistdata.AlbumRelease}
/>
)
)}
</div>
</div>
)
}
export default Music
music.css
* {
margin: 0 auto;
padding: 0;
box-sizing: border-box;
}
form > :first-child {
outline: none;
border: 1px solid #aaa;
transition: all .3s;
border-radius: 2px;
}
form > :first-child {
width: 400px;
font-size: 18px;
height: 24px;
padding: 2px 8px;
}
form > :nth-child(1):focus {
box-shadow: 0 0 7px #1abc9c;
border: 1px solid #1abc9c;
}
form > :last-child {
margin-top: 4px;
margin-left: 7px;
font-size: 16px;
height: 40px;
padding: 2px 8px;
}
form button {
border: 1px solid #ccc;
background-color: #FFFFFF;
border-radius: 2px;
cursor: pointer;
box-shadow: 0px 2px 2px 0px rgba(0,0,0,.1);
}
form button:hover {
box-shadow: 0px 2px 2px 2px rgba(0, 0, 0, .1);
}
.result {
text-align: center;
}
.searchresult {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.noalbum{
display: none;
}
.flex {
padding: 3px 1px;
display: flex;
flex-direction: row;
}
.flex > :nth-child(2) {
padding-top: 1.7em;
padding-bottom: 0.5em;
}
.flex > :nth-child(2):nth-child(2){
font-family: 'Courier New', Courier, monospace;
}
.album {
width: 300px;
height: 230px;
overflow: hidden;
margin: 10px 8px 10px 16px;
position: relative;
border: ridge 10px #87CEFA;
}
.mask {
width: 100%;
height: 100%;
position: absolute;
top: -100%;
opacity: 0; /* マスクにする */
background-color: rgba(0,0,0,0.4);
transition: all 0.6s ease;
}
.caption {
font-size: 130%;
text-align: center;
color: #fff;
}
img.alabumImage {
width: 80%;
height: 80%;
position: absolute;
}
.album:hover .mask {
opacity: 1; /* マスクを完全に不透明表示する */
padding-top: 80px; /* ホバーで下にずらす */
top: 0;
}