- 投稿日:2021-12-05T23:16:39+09:00
S3のフォルダ内の写真をフォトシャワーっぽくjsで表示してみる
動機 ・フォトシャワーはなんかいい感じに写真がスクリーンの上から降ってくるみたいな演出です。 ・結婚式の披露宴のときに、披露宴でとった写真がリアルタイムでフォトシャワーで表示されたらおもしろそうだなあと思って結婚式の披露宴のために作りました。 S3は関係なくとりあえずローカルでも動くコードをかく ・この下のコードをコピペしてimage_list内の写真をそれぞれの環境の写真に変更すれば、とりあえずこんな感じで写真が上から下に降りてくるみたいな感じのことができてイメージができるはず。 ・image_list内の写真を全部表示したら、S3からAPIであらたに写真をとってきて、image_listを作り直すという感じです。(S3からの写真の取得は別記事にします) ・披露宴中にどんどん写真がS3の方にたまっていくので、ちょこちょこ写真を取りに行くスタイルにしました。 photo_shower.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Wedding_photo_shower</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <style> .photo-shower-container { position: relative; height: 100vh; /* コンテナの高さ */ width: 100%; /* コンテナの横幅 */ overflow: hidden; /* コンテナからはみ出した要素を隠す */ background-image: linear-gradient(to right, #FFECD2 0%, #FCB69F 100%); } /* 写真のスタイル */ .picture { position: absolute; animation: animate-picture 10s linear; } .pic { max-width: 600px; max-height: 600px; } /* 写真のアニメーション */ @keyframes animate-picture { 0% { top: 0; opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { opacity: 0; top: 100vh; } } .content { position: relative; } </style> </head> <body> <main class='content'> <div class='photo-shower-container'></div> </main> </body> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> window.addEventListener('DOMContentLoaded', () => { // はじめに表示される写真たち。ここのファイル指定をそれぞれの環境に合わせるととりあえずうごく let image_list = [ 'image_cat/cat1.jpeg', 'image_cat/cat2.jpeg', 'image_cat/cat3.jpeg', 'image_cat/cat4.jpeg', 'image_cat/cat5.jpeg', 'image_cat/cat6.jpeg', 'image_cat/cat7.jpeg', 'image_cat/cat8.jpeg', 'image_cat/cat9.jpeg', 'image_cat/cat10.jpeg' ]; const section = document.querySelector('.photo-shower-container'); let field = 'right'; // 左右交互に写真を表示させるため let count = 0; // 写真を生成する関数 function createPic() { const pictureEl = document.createElement('span'); pictureEl.className = 'picture'; let image = document.createElement('img'); image.className = 'pic'; image.src = image_list[count]; image.style.borderRadius = '20px'; // 写真の傾き具合を-30〜30degでランダムに let min = 0 ; let max = 60 ; let rotate_num = Math.floor( Math.random() * (max + 1 - min) ) + min - 30; image.style.transform = 'rotate(' + rotate_num + 'deg)'; // 写真の長辺の長さを指定 const minSize = 400; const maxSize = 600; let size = Math.random() * (maxSize + 1 - minSize) + minSize; // ここで写真のむきを取得。 getPictureDirection(image.src) // dataにはwidthかheight。長い方がstringで入っている。 .then((data) => { if(data === 'width') { image.style.width = size + 'px'; image.style.height = 'auto'; } else { image.style.height = size + 'px'; image.style.weight = 'auto'; } pictureEl.appendChild(image); // 左右の順番に写真が表示されるように if (field === 'right') { pictureEl.style.left = Math.random() * 300 + 'px'; field = 'left'; } else { pictureEl.style.left = Math.random() * 475 + 475 + 'px'; field = 'right' } section.appendChild(pictureEl); }) .catch((err) => { console.log("error", err); }); // 一定時間が経てば写真を消す setTimeout(() => { pictureEl.remove(); }, 10000); count += 1; // image_listを全て表示したら、APIで次に表示させる写真を取得しimage_listを作成。 if ( count >= image_list.length ) { // APIに関しては別記事参照。 changePicture(modify_time).done(function(data,textStatus,jqXHR) { console.log("成功:" + jqXHR.status); image_list = []; // 取得したファイル名を表示するimage_listに入れる $.each(data,(index,value) => { image_list.push(value.filename); modify_time = value.last_modified; }); }).fail(function(jqXHR, textStatus, errorThrown) { console.log("ajax失敗:" + jqXHR.status); console.log(textStatus, errorThrown); }); count = 0; } } // 写真を生成する間隔をミリ秒で指定 setInterval(function () { createPic() }, 2500); }); // APIでimage_listの中身を取得。別記事参照。 function changePicture(modify_time){ return $.ajax({ url:'*****', type:'POST', data:{body:modify_time}, dataType:'json', // 応答のデータの種類 (xml/html/script/json/jsonp/text) contentType: "application/json;charset=UTF-", accessControlAllowOrigin: '*', headers:{ 'x-api-key': '*****' } }) } // 写真を読み込んでから向きの判定 const loadImg = function(src){ return new Promise(function(resolve, reject){ const image = new Image(); image.src = src; image.onload = function(){ resolve(image); } image.onerror = function(error){ reject(error); } }); } // 写真の向きを取得 let getPictureDirection = function getDirection(src) { return new Promise(function(resolve, reject){ loadImg(src) //読み込みが完了した(画像が設定された)Imageオブジェクトを受け取って処理 .then(function(res){ if (res.width > res.height) { resolve('width'); } else { resolve('height'); } }) //読み込みエラー時の処理 .catch(function(error){ console.log(error); }); }); } </script> 補足 ・結婚式では自分のmacと会場のスクリーンをつなぐ感じだったので、自分のmacでいい感じに表示されればよいかなというcssです。 ・写真の大きさは最初はmax-heightとmax-widthを指定するだけだったのですが、いまいちだったので、ちゃんと写真の向きを取得してから、maxの値を決めました。写真をロードするのをまたないといけなかったので、ここでコードが結構増えてしまった。。 ・実際に会場でリハーサルをしたときに写真が見にくかったので、本番は背景を黒にしました。実際表示してみないと分からない部分だなあと思いました。 ・ひとまず結婚式は良い感じだったのでよし!
- 投稿日:2021-12-05T23:16:39+09:00
JSでフォトシャワーを自作してみた
動機 ・結婚式でみんながとった写真すぐに共有できたらなあ ・いい感じにフォトシャワーみたいなもの作ってみよかなあ とりあえずコードを貼ってみる ・これをコピペしてimage_list内の写真を変更すればとりあえずこんな感じで写真が上から下に降りてくるみたいな感じのことができるはず、、(写真なのでわかりにくいですが) photo_shower.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Wedding_photo_shower</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <style> .photo-shower-container { position: relative; height: 100vh; /* コンテナの高さ */ width: 100%; /* コンテナの横幅 */ overflow: hidden; /* コンテナからはみ出した要素を隠す */ background-image: linear-gradient(to right, #FFECD2 0%, #FCB69F 100%); } /* 写真のスタイル */ .picture { position: absolute; animation: animate-picture 10s linear; } .pic { max-width: 600px; max-height: 600px; } /* 写真のアニメーション */ @keyframes animate-picture { 0% { top: 0; opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { opacity: 0; top: 100vh; } } .content { position: relative; } </style> </head> <body> <main class='content'> <div class='photo-shower-container'></div> </main> </body> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> window.addEventListener('DOMContentLoaded', () => { let image_list = [ 'image_cat/cat1.jpeg', 'image_cat/cat2.jpeg', 'image_cat/cat3.jpeg', 'image_cat/cat4.jpeg', 'image_cat/cat5.jpeg', 'image_cat/cat6.jpeg', 'image_cat/cat7.jpeg', 'image_cat/cat8.jpeg', 'image_cat/cat9.jpeg', 'image_cat/cat10.jpeg' ]; // コンテナを指定 const section = document.querySelector('.photo-shower-container'); let field = 'right'; // 左右交互に写真を表示させるため let count = 0; // 写真を生成する関数 function createPic() { const pictureEl = document.createElement('span'); pictureEl.className = 'picture'; let image = document.createElement('img'); image.className = 'pic'; image.src = image_list[count]; image.style.borderRadius = '20px'; // 写真の傾き具合を-30〜30degでランダムに let min = 0 ; let max = 60 ; let rotate_num = Math.floor( Math.random() * (max + 1 - min) ) + min - 30; image.style.transform = 'rotate(' + rotate_num + 'deg)'; // 写真の長辺の長さを指定 const minSize = 400; const maxSize = 600; let size = Math.random() * (maxSize + 1 - minSize) + minSize; // ここで写真のむきを取得。 getPictureDirection(image.src) // dataにはwidthかheight。長い方がstringで入っている。 .then((data) => { if(data === 'width') { image.style.width = size + 'px'; image.style.height = 'auto'; } else { image.style.height = size + 'px'; image.style.weight = 'auto'; } pictureEl.appendChild(image); // 左右の順番に写真が表示されるように if (field === 'right') { pictureEl.style.left = Math.random() * 300 + 'px'; field = 'left'; } else { pictureEl.style.left = Math.random() * 475 + 475 + 'px'; field = 'right' } section.appendChild(pictureEl); }) .catch((err) => { console.log("error", err); }); // 一定時間が経てば写真を消す setTimeout(() => { pictureEl.remove(); }, 10000); count += 1; } // 写真を生成する間隔をミリ秒で指定 setInterval(function () { createPic() }, 2500); }); // 写真を読み込んでから向きの判定 const loadImg = function(src){ return new Promise(function(resolve, reject){ const image = new Image(); image.src = src; image.onload = function(){ resolve(image); } image.onerror = function(error){ reject(error); } }); } // 写真の向きを取得 let getPictureDirection = function getDirection(src) { return new Promise(function(resolve, reject){ loadImg(src) //読み込みが完了した(画像が設定された)Imageオブジェクトを受け取って処理 .then(function(res){ if (res.width > res.height) { resolve('width'); } else { resolve('height'); } }) //読み込みエラー時の処理 .catch(function(error){ console.log(error); }); }); } </script> 補足 ・結婚式では自分のmacと会場のスクリーンをつなぐ感じだったので、自分のmacでいい感じに表示されればよいかなというcssです。 ・写真の大きさは最初はmax-heightとmax-widthを指定するだけだったのですが、いまいちだったので、ちゃんと写真の向きを取得してから、maxの値を決めました。写真をロードするのをまたないといけなかったので、ここでコードが結構増えてしまった。。 ・実際に会場でリハーサルをしたときに写真が見にくかったので、本番は背景を黒にしました。実際表示してみないと分からない部分だなあと思いました。 ・ひとまず結婚式は良い感じだったのでよし!
- 投稿日:2021-12-05T20:38:50+09:00
ツールチップの表示・非表示を切り替えるためにstopPropagationを使った例
この記事について ツールチップを表示・非表示にする実装で詰んでたので忘れないようにメモ 文字だけだと分かりづらいので挙動イメージは下の方でcodepenにまとめておいた ツールチップ表示・非表示の仕様 ツールチップ内の「×」を押すと消える 「?」を押すと表示される ツールチップの外をクリックすると消える ツールチップが開くのは「?」を押したアイテムのみ NG挙動 連続で「?」をクリックしても反応しない ツールチップ内をクリックするとツールチップが消えてしまう See the Pen Untitled by kena-nk (@kena-nk) on CodePen. コードはこんな感じ function Base() { const [question, setQuestion] = React.useState(0); const toggleDescription = (e, name) => (question === name ? setQuestion(null) : setQuestion(name)); const closeDescription = () => { if (question !== null) setQuestion(null); }; return ( <div className="container" onClick={() => closeDescription()}> <div className="list"> <div className="item"> <div>項目1</div> <div className="wrapper"> <div className="far fa-question-circle" onClick={(e) => toggleDescription(e, 1)} /> {question === 1 && ( <div className="description"> <div>項目1の説明が入るよ</div> <div className="far fa-times-circle" onClick={(e) => toggleDescription(e, 1)} /> </div> )} </div> </div> ~~省略~~ </div> </div> ); } OK挙動 連続で「?」をクリックしても反応しない → 解決 ツールチップ内をクリックするとツールチップが消えてしまう → 解決 See the Pen ツールチップOK挙動 by kena-nk (@kena-nk) on CodePen. コードはこんな感じ function Base() { const [question, setQuestion] = React.useState(0); const toggleDescription = (e, name) => { e.stopPropagation(); // e.stopPropagation();追加したよ return question === name ? setQuestion(null) : setQuestion(name); }; const closeDescription = () => { if (question !== null) setQuestion(null); }; return ( <div className="container" onClick={() => closeDescription()}> <div className="list"> <div className="item"> <div>項目1</div> <div className="wrapper"> <div className="far fa-question-circle" onClick={(e) => toggleDescription(e, 1)} /> {question === 1 && ( // onClick={(e) => e.stopPropagation()}追加したよ <div className="description" onClick={(e) => e.stopPropagation()}> <div>項目1の説明が入るよ</div> <div className="far fa-times-circle" onClick={(e) => toggleDescription(e, 1)} /> </div> )} </div> </div> ~~省略~~ </div> </div> ); } 今回の原因 一番外側のDOMに仕掛けてるcloseDescription()がクリックのたびに実行されていることが問題点だった。 本当は内側のDOMごとに仕掛けているtoggleDescription()が発火されてほしいが、親のイベント実行が邪魔してうまくいってないという感じだった。 流れに起こすとこんな感じ↓ 1. 項目1の「?」クリック 2. toggleDescription() が発火して question に 1 がセットされる 3. closeDescription() が発火するが question は null ではないので何も起こらない 4. 項目2の「?」クリック 5. toggleDescription() が発火して question に 2 がセットされる 6. closeDescription() が発火して question は値がセットされている為 question に null セットされる なので今回は親の実行を阻止するstopPropagation()を仕掛けることで回避した。 まとめ イベント発火阻止系はpreventDefault()しか使ったことがなく色々疎かったので使う機会が来てよかった。めちゃめちゃ有難い関数・・! これ以外にも親のイベントを阻止する方法などあればどなたか教えてください?
- 投稿日:2021-12-05T19:06:53+09:00
Webの勉強はじめてみた その9 ~JavaScriptとCSS編 簡単なアニメーション~
N予備校の「プログラミング入門 Webアプリ」をアーカイブ動画とともに受講しています。 今回は第一章13節「はじめてのCSS」と14節「CSSを使ったプログラミング」を受講しました。 メモ 1. marginは要素の外側、paddingは要素の内側 2. CSSではclass属性を使うことが多い 3. 条件文はできるだけシンプルに 練習問題: JavaScriptとCSSでアニメーション <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title> CSS を使ったプログラミング </title> <link rel="stylesheet" href="css-programming.css"> </head> <body> <h1 id="header" class="face">CSS を使ったプログラミング</h1> <script src="animation.js"></script> </body> </html> .face{ /*color: darkblue; */ color: red; } .back{ /*color: lightgray;*/ color: red; /*opacity: 0.4;*/ } //header 取得 let header = document.getElementById('header'); //角度 let degree = 0; //透明度 let opacity = 0; //加算フラグ let isPositive = true; //headerを回転させる function rotateHeader(){ //6°ずつ回転させる degree += 6; //裏を向いた瞬間にclassを変更する //361° と 1° は同じなので、360で割った余りが現在の角度になる。 degree %= 360; //表を向いているとき:0 以上 90 未満 か 270 以上 360 未満 /*if((0 <= degree && degree < 90) || (270 <= degree && degree < 360)){ //表 header.className = 'face'; }else{ //裏を向いているとき:90 以上 270 未満 header.className = 'back'; }*/ //↓↓ 条件をシンプルに if(90 <= degree && degree < 270){ header.className = 'back'; }else{ header.className = 'face'; } //回転 header.style.transform = 'rotateX(' + degree +'deg)'; //header.style.transform = 'rotate3d(1, 1, 1, ' + degree +'deg)'; //透明度の設定 if(isPositive){ opacity += 0.1; if(opacity >= 1){ //加算フラグを消す isPositive = false; } }else{ opacity -= 0.1; if(opacity <= 0){ //加算フラグを立てる isPositive = true; } } header.style.opacity = opacity; } //20ミリ秒ごとに実行してアニメーションさせる //setInterval(rotateHeader, 20); setInterval(rotateHeader, 100); 今回はh1タグ内の文字列を回転させる、というもの。 あと追加で透明度も変えてます。 degree %= 360 のあたり、自力では全然思いつかないので、ただすごいなぁと感心するばかりでした。 まとめ アニメーションって手段は色々あるけれど、どれも個人的にすごく感動を覚えます。 あとはもっと頭を柔らかくしていきたい。 そういえば次はCSS編です、と言いながらメインはがっつりJavaScriptでした。
- 投稿日:2021-12-05T15:30:12+09:00
4.4 FlexBox
FlexBox 「Flexible Box Layout Module」の略でレイアウトの作成でよく使用されます。 例えば、要素を横並びに配置したいときや、要素を上下中央に配置したい時など。 ↓とてもわかりやすいサイトがありましたのでこちらで説明していきます。 https://webdesign-trends.net/entry/8148#CSS_Flexbox よく使用する属性 基本以下5つの属性を覚えておけばほとんど実現できるようです。 左右をピッタリで等間隔justify-content: space-between; 横並びから縦並びに変更flex-direction: column; 折返しflex-wrap: wrap; 順番を入れ替えるflex-direction: row-reverse; 上下を中央にそろえるalign-items: center; 今回はこちらのボックスを使用して説明していきます。 CSSの復習も兼ねて実際にコピペして画面表示させてみましょう。 index.html <div class="item"> <div class="item-img"> <img src="https://haniwaman.com/cms/wp-content/uploads/2018/10/dummy.jpg" alt="" /> </div> <div class="item-body"> <div class="item-title">見出し見出し見出し見出し</div> <p> テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト </p> </div> </div> style.css .item { width: 100%; } img { width: 100%; } .item-body { padding: 0; } .item-title { margin: 0 0 8px; font-size: 20px; font-weight: 700; color: #ef5350; } .item-body p { font-size: 16px; font-weight: 400; color: #333; } 左右をピッタリで等間隔justify-content: space-between; ①ボックスを3つ並べて、<div class="items"></div>で囲みます。 ※使用するボックスの2つ目、3つ目は以下にすると見やすくなります。 2つ目https://haniwaman.com/cms/wp-content/uploads/2018/10/dummy2.jpg 3つ目https://haniwaman.com/cms/wp-content/uploads/2018/10/dummy3.jpg ②スタイルを追加します .items { display: flex; justify-content: space-between; } .items .item { width: 30%; } 横並びから縦並びに変更flex-direction: column; row(横並び)が一般的だと思いますが、columnで縦並びにもできます。 レスポンシブ時によく使用しますので、画面幅が767pxまではスマホ画面でのレイアウトでよくある縦並びになるようにします。 以下スタイルを追加。 @@media screen and (max-width: 767px) { .items { flex-direction: column; } .items .item { width: 100%; } } 折返しflex-wrap: wrap; index.htmlにこちらを追加してください。 <div class="item"> <div class="item-img"> <img src="https://haniwaman.com/cms/wp-content/uploads/2018/10/dummy4.jpg" alt="" /> </div> <div class="item-body"> <div class="item-title">見出し見出し見出し見出し</div> <p> テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト </p> </div> </div> <!-- /.item --> <div class="item"> <div class="item-img"> <img src="https://haniwaman.com/cms/wp-content/uploads/2018/10/dummy5.jpg" alt="" /> </div> <div class="item-body"> <div class="item-title">見出し見出し見出し見出し</div> <p> テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト </p> </div> </div> <!-- /.item --> <div class="item"> <div class="item-img"> <img src="https://haniwaman.com/cms/wp-content/uploads/2018/10/dummy6.jpg" alt="" /> </div> <div class="item-body"> <div class="item-title">見出し見出し見出し見出し</div> <p> テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト </p> </div> </div> <!-- /.item --> そのままだと画面幅いっぱいに要素が表示されているかと思います。 これはclass="item"に対してwidth: 30%と指定しているにも関わらず、Flexboxが横幅いい感じに調整して横並びにしていることが原因です。 この問題を解消するために、(画面幅を越えるように横幅を指定した場合)折り返されるようにしなければいけません。 そこでclass="items"にflex-wrap: wrap;を追加。 以下のスタイルになります。 .items { display: flex; justify-content: space-between; flex-wrap: wrap; } 順番を入れ替えるflex-direction: row-reverse; 2.で並びの向きを指定する属性flex-directionがありましたが、これを使用して逆向きにもできます。 全ての要素を逆向きにしても面白くないので、奇数だけ逆向きにしていきます。 ①画像の横に見出しとテキストを配置します。.items .itemのスタイルを以下に変更。 .items .item { /* width: 30%; */ display: flex; margin: 0 0 24px; } ②画像にスタイルを微調整します。 ※こちらはあっても無くても良い(今回の要素の順番を変更することに直接関係しないため) .items .item-img { width: 50%; padding: 0 12px; } ③奇数の場合に並びを逆にするスタイルを追加。 セレクタの語尾に:nth-child(odd)を追加することで実現できます。 また、偶数の場合は:nth-child(even)でできます。 その他は要素の順番の指定方法の詳細↓ http://ideahacker.net/2013/06/28/5571/ .items4 .item:nth-child(odd) { flex-direction: row-reverse; } 上下を中央にそろえるalign-items: center; ヘッダーで使用する場面がよくあるので、先ほどの記事風の画面にヘッダーを追加して説明します。 こちらを追加 index.htmlの<body>に追加(ヘッダなのでbodyの次の行がいいかと) <header> <div class="items5"> <div class="item-logo"><a href="">ロゴ</a></div> <nav class="item-nav"> <ul> <li><a href="">Top</a></li> <li><a href="">About</a></li> <li><a class="active" href="">Blog</a></li> <li><a href="">Profile</a></li> <li><a href="">Contact</a></li> </ul> </nav> <!-- /.item-nav --> </div> </header> 以下CSSに追加。 .items5 { display: flex; align-items: center; justify-content: space-between; height: 100px; box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.16); padding: 0 24px; } .item-logo > a { text-decoration: none; color: #333; } .item-nav li { display: inline-block; padding: 0 12px; } .item-nav a { text-decoration: none; color: #333333; } .item-nav a.active, .item-nav a:hover { border-bottom: 3px solid #ef5350; } 参考 https://webdesign-trends.net/entry/8148#CSS_Flexbox https://haniwaman.com/flexbox/ http://ideahacker.net/2013/06/28/5571/
- 投稿日:2021-12-05T11:33:20+09:00
「教科書では教えてくれないHTML&CSS」読書録
HTMLやCSSを学んで理解は出来たが、どう書けば良いかが分からなかったので参考になりました。 忘れないように気になった部分の要約を残しておきます。 デザインを分割し、小さなパーツに分解する デザイン画像を元に大まかに次の2点で分割する。 横幅が変わるところ 背景の塗りつぶし方法が変わるところ その際、分割したパーツごとのHTML構造は二重構造にする。 <div class="分割したパーツ名"> <div class="〇〇-container"> </div> </div> 二重構造となったパーツに対して適用するスタイル <div class="〇〇-container">に適用するスタイル パーツの幅 パーツをビューポートの中央に配置するスタイル 上下左右の空きスペースを調整するスタイル <div class="分割したパーツ名">に適用するスタイル 背景色や背景画像 分割されたパーツを内容やレイアウトに合わせて更に分割する 更に単純なパーツごとに分けて、HTMLとCSSを書いていく。 そうすることで大きな問題を小さくて手のつけやすい目標に変えてしまう。 また、これにより各部分がモジュール化され使い回しがしやすくなる。 更新しやすいCSSの書き方 優先度を低く保つ 子孫セレクタなどで複数のセレクタを一度に使う際には、 数が多いほど優先度が高くなってしまう為、 子孫セレクタを使うときは使うセレクタの数を最小限に抑え、優先度を低く保つ。 例 <div class="block"> <ul class="list"> <li class="item">...</li> </ul> </div> 上記の<li class="item">にスタイルを適用したい場合のセレクタは次のリストのようになるが、 リストの上に行くほど良い書き方になる。 .item { } ◯ .list .item { } △ .block .list .item { } ☓ スタイルの位置を意識する あとから読み込まれたスタイルのほうが優先度が高くなるので、 より多くの要素に適用されるスタイルを上の方に、特定の要素だけに適用されるスタイルを下の方に書く。 セレクタの優先順位 1.HTMLタグのstyle属性 2.idセレクタ 3.classセレクタなど(2と4を除く多くのセレクタ) 4.タイプセレクタと疑似要素 5.全称セレクタ (*) レスポンシブデザインのための心得 ブロックボックスにむやみに幅を指定しない。 端末の画面サイズはまちまちなので、ブロックボックスの親要素の幅いっぱいに広がる特性を利用する。 画面サイズに合わせてレイアウトを切り替える必要があるときだけ、メディアクエリを使う。 フレックスボックスとグリッドレイアウトの使い分け ボックスを横一列に並べるときはフレックスボックス、複数行に並べたいときはグリッドレイアウト。 ボックスのサイズをガッチリ決めたいならグリッドレイアウト、サイズ自体は柔軟に対応したいならフレックスボックス。 フレックスボックスでは1つひとつのボックスのサイズは原則としてコンテンツの長さで決まる為、 ボックスのサイズが変わっても良いときに有利。 レスポンシブデザインに対応した画像のスタイル img { max-width: 100%; /* 親要素の幅に収まるようにする */ vertical-align: bottom; /* 画像の下に空くスペースを消す */ height: auto; /* 古いブラウザで縦横比を維持する */ } flexプロパティで使う設定は大体3パターンのみ flex: 伸長比 縮小比 ベース幅; flex: 0 0 子要素の幅px; 親要素の幅に関わらず、子要素の幅はベース幅で固定。 flex: 1 1 auto; 親要素に合わせてボックスが伸縮する。 flex: 0 1 auto; デフォルト値 親要素が狭いときは子要素も縮小、親要素が広くてもベース幅を維持して伸長しない。 ベース幅とは 親要素の幅に余裕がある時、子要素の幅は自身のコンテンツが収まる長さになる、この長さをベース幅という。
- 投稿日:2021-12-05T06:02:11+09:00
【CSS3】CSSカウンター(counter関数)
概要 「セクション1..2..3..」などの連番を実装したい場合に有効的である、カウンターをCSSで実現する方法を記載する。 この記事で理解できること CSSでのカウンター作成方法 CSSのcounter関数の使用方法 counter関数の関連プロパティ、関連要素 必要なCSSプロパティ プロパティ名 内容 書式 counter-reset カウンター(値を管理する変数)の初期値を設定する※このプロパティを指定したタグやclassが出てくるたびに値が初期化される counter-reset: {値を管理したい変数名} {初期値}; counter-increment 値の増加値を設定 counter-increment: {変数名} {増加数} content 表示形式を指定(counter関数はこのプロパティで使用) {付加値(前)} content: counter({変数名}) {付加値(後)} contentプロパティについて 「ポイント1:」や「第1章:」などと表示したい場合、{付加値(前・後)}と記載している箇所に値を指定することでカウントする値の前後(「ポイント」や「:」にあたる箇所)の表示を設定できる。 作成の流れ ① カウンター変数に初期値を設定(初期化) ② 値の増加数を指定 ③ 表示形式を指定 ★ カウンターはある要素の擬似要素(:beforeなど)を用いて使用することが多い。 ★ ①は実際にカウンター値を使用するタグ(要素)を囲っているタグ(要素)に指定する必要があります。 実際に例を見た方が理解できると思いますので、次へいきましょう。 実際に作成してみる ※bodyタグ・タグ内の要素以外は省略しています。 作成例① <body> <ul class="menu-list"> <li class="menu-item">メニューアイテム</li> <li class="menu-item">メニューアイテム</li> <li class="menu-item">メニューアイテム</li> </ul> </body> style.css /* liタグデフォルトの「・」を非表示にしておきたいので設定 */ li { list-style: none; } /* 今回ulタグに指定したclass */ .menu-list { counter-reset: number 0; /* ① カウンター変数に初期値を設定 : 今回は「number」という変数名で値を管理し、初期値を0に設定 */ } /* 今回liタグに指定したclassの擬似要素へ指定 */ .menu-item::before { counter-increment: number 1; /* ② 値の増加数を指定 : 今回は1ずつ増やしたいのでnumberに1を指定 */ content: counter(number) ' '; /* ③ 表示形式を指定 : counter関数の引数には①ので作成した変数を指定し、カウンター値の後ろにスペースを指定 */ } 作成例② <body> <ul class="menu-list"> <li class="menu-item">旅立ち</li> <li class="menu-item">出会い</li> <li class="menu-item">別れ</li> </ul> </body> /* liタグデフォルトの「・」を非表示にしておきたいので設定 */ li { list-style: none; } /* 今回ulタグに指定したclass */ .menu-list { counter-reset: number 0; /* ① カウンター変数に初期値を設定 : 今回は例として「number」という値を管理する変数名で初期値を0に設定 */ } /* 今回liタグに指定したclassの擬似要素へ指定 */ .menu-item::before { counter-increment: number 1; /* ② 値の増加数を指定 : 今回は1ずつ増やしたいのでnumberに1を指定 */ content: '第' counter(number) '章: '; /* ③ 表示形式を指定 : 今回はカウンター値の前後に付加値を設定 */ } counter関数のスタイル(style) counter関数にCSS側で用意されているスタイル(style)を指定することで、以下のようなスタイル設定を行うことができる。(作成例②の例を漢数字にしたい場合など) 参考資料 : CSS Counter Styles Level 3 <!-- 作成例②と同じ --> <body> <ul class="menu-list"> <li class="menu-item">旅立ち</li> <li class="menu-item">出会い</li> <li class="menu-item">別れ</li> </ul> </body> /* liタグデフォルトの「・」を非表示にしておきたいので設定 */ li { list-style: none; } /* 今回ulタグに指定したclass */ .menu-list { counter-reset: number 0; /* ① カウンター変数に初期値を設定 : 今回は例として「number」という値を管理する変数名で初期値を0に設定 */ } /* 今回liタグに指定したclassの擬似要素へ指定 */ .menu-item::before { counter-increment: number 1; /* ② 値の増加数を指定 : 今回は1ずつ増やしたいのでnumberに1を指定 */ content: '第' counter(number, cjk-ideographic) '章: '; /* ③ 表示形式を指定 : counter関数の漢数字スタイルを指定 */ }
- 投稿日:2021-12-05T05:54:05+09:00
【CSS3】CSSカウンター(counter関数)
概要 「セクション1..2..3..」などの連番を実装したい場合に有効的である、カウンターをCSSで実現する方法を記載する。 この記事で理解できること CSSでのカウンター作成方法 CSSのcounter関数の使用方法 counter関数の関連プロパティ、関連要素 必要なCSSプロパティ プロパティ名 内容 書式 例 counter-reset カウンター(値を管理する変数)の初期値を設定する counter-reset: {値を管理したい変数名} {初期値}; counter-reset: number 0; counter-increment 値の増加値を設定 counter-increment: {変数名} {増加数} counter-increment: number 1; content 表示形式を指定(counter関数はこのプロパティで使用) {付加値(前)} content: counter({変数名}) {付加値(後)} content: counter(number) ' '; contentプロパティについて 「ポイント1:」や「第1章:」などと表示したい場合、{付加値(前・後)}と記載している箇所に値を指定することでカウントする値の前後(「ポイント」や「:」にあたる箇所)の表示を設定できる。 作成の流れ ① カウンター変数に初期値を設定(初期化) ② 値の増加数を指定 ③ 表示形式を指定 ★ カウンターはある要素の擬似要素(:beforeなど)を用いて使用することが多い。 ★ ①は実際にカウンター値を使用するタグ(要素)を囲っているタグ(要素)に指定する必要があります。 実際に例を見た方が理解できると思いますので、次へいきましょう。 実際に作成してみる ※bodyタグ・タグ内の要素以外は省略しています。 作成例① <body> <ul class="menu-list"> <li class="menu-item">メニューアイテム</li> <li class="menu-item">メニューアイテム</li> <li class="menu-item">メニューアイテム</li> </ul> </body> style.css /* liタグデフォルトの「・」を非表示にしておきたいので設定 */ li { list-style: none; } /* 今回ulタグに指定したclass */ .menu-list { counter-reset: number 0; /* ① カウンター変数に初期値を設定 : 今回は「number」という変数名で値を管理し、初期値を0に設定 */ } /* 今回liタグに指定したclassの擬似要素へ指定 */ .menu-item::before { counter-increment: number 1; /* ② 値の増加数を指定 : 今回は1ずつ増やしたいのでnumberに1を指定 */ content: counter(number) ' '; /* ③ 表示形式を指定 : counter関数の引数には①ので作成した変数を指定し、カウンター値の後ろにスペースを指定 */ } 作成例② <body> <ul class="menu-list"> <li class="menu-item">旅立ち</li> <li class="menu-item">出会い</li> <li class="menu-item">別れ</li> </ul> </body> /* liタグデフォルトの「・」を非表示にしておきたいので設定 */ li { list-style: none; } /* 今回ulタグに指定したclass */ .menu-list { counter-reset: number 0; /* ① カウンター変数に初期値を設定 : 今回は例として「number」という値を管理する変数名で初期値を0に設定 */ } /* 今回liタグに指定したclassの擬似要素へ指定 */ .menu-item::before { counter-increment: number 1; /* ② 値の増加数を指定 : 今回は1ずつ増やしたいのでnumberに1を指定 */ content: '第' counter(number) '章: '; /* ③ 表示形式を指定 : 今回はカウンター値の前後に付加値を設定 */ } counter関数のスタイル(style) counter関数にCSS側で用意されているスタイル(style)を指定することで、以下のようなスタイル設定を行うことができる。(作成例②の例を漢数字にしたい場合など) 参考資料 : CSS Counter Styles Level 3 <!-- 作成例②と同じ --> <body> <ul class="menu-list"> <li class="menu-item">旅立ち</li> <li class="menu-item">出会い</li> <li class="menu-item">別れ</li> </ul> </body> /* liタグデフォルトの「・」を非表示にしておきたいので設定 */ li { list-style: none; } /* 今回ulタグに指定したclass */ .menu-list { counter-reset: number 0; /* ① カウンター変数に初期値を設定 : 今回は例として「number」という値を管理する変数名で初期値を0に設定 */ } /* 今回liタグに指定したclassの擬似要素へ指定 */ .menu-item::before { counter-increment: number 1; /* ② 値の増加数を指定 : 今回は1ずつ増やしたいのでnumberに1を指定 */ content: '第' counter(number, cjk-ideographic) '章: '; /* ③ 表示形式を指定 : counter関数の漢数字スタイルを指定 */ }
- 投稿日:2021-12-05T00:37:31+09:00
現役ミュージシャンが実務未経験から実務案件に挑戦。コードを書く事だけがエンジニアの仕事ではないと気付かされる!
自己紹介 初めまして!現在音楽講師をしているhirokiと申します。 未経験からエンジニアへの転職を目指し日々学習をしております。 楽器歴はもう17年と日々演奏技術を磨きながら最新の音楽情報をキャッチアップしております。 そんな私がプログラミングを始めたのは、別のミュージシャンの方が自分のホームページを持っていたので「僕も作ってみたいな」という気持ちからでした。 そこからプログラミングについて調べみるとそのページにはログイン機能などがあり、これはなんだと?と思い調べてみました。 調べていくと、Webアプリケーションが作れ、雛形が揃っていて尚且つ学びやすいというフレームワークがあるという記事を見つけました。そこで出会ったのがRuby on Railsでした。まずは始めないと正解・不正解も分からないので、何事も触れてみようと思い学習をスタートしました。 独学で勉強し少しずつ理解が深まるごとにどんどんプログラミングにハマっていきました。 ある出来事で転職したいと思うようになりました。それは、目の前でWebサービスを使っている生徒さんの困っている姿でした。 生徒さんから『バンドを組んでライブをしたいのにメンバーが見つからないです』っという相談を頂きました。私自身バンドメンバー募集サイトを利用し、新たなメンバーと出会えた経験があることから、そのサービスを勧めました。 しかし生徒様から、『自分で探さなくても自動で紹介してくれる機能が欲しい、探しにくいな』と感想を頂きました。 『そのような機能をがあるのかな?』と思い調べていくとある機能に注目しました。 それはプロフィールや求める人物を自動でマッチングしてくれるようなシステムです。きっかけは恋愛マッチングアプリのシステムからです。 しかし、構想があってもスキルがなければ実現できません。 『私がサービスを改善する事で問題解決し、役に立ちたいな』と思うようになりました。 目の前に困っている生徒さんがいるのに、改善出来ないことへの悔しさが込み上げてきました。 悔しい経験から、技術探求し、IT技術を通し、『人の役に立てようになりたい』と思うようなりました。 この経験から私自身エンジニアとして成長し、IT技術を通じ、人々の役に立ちたいと思う様になりエンジニアへの転職を決意しました。 案件の概要 実務案件を頂けたのですが、今回クライアントから依頼があった内容は、医院紹介サイト(Rails)のSEOの改善とCore Web Vitalsの改善をしてほしいという案件内容でした。 チームメンバーでタスクを分けしたのですが、私が担当させて頂いたのはCore Web Vitalsの改善でした。(Core Web Vitalsの詳しい内容は後述) メンバー構成と実務期間は、リーダーであるY氏と、私含め3名の未経験者が2ヶ月間実務に取り組みました。 実務案件を申し込んだ理由と経緯 応募したきっかけは、入社前後のギャップを埋めたいと思い応募しました。実務を体験をすることによって独学でプログラミングに触れている時と実務とのギャップを少しでも埋めることができ、入社後の成長、心構えを知る事が出来るのではないかと考えたからです。 もう一つは書いてあることしかできない(言われたことしかできない)人間ではなく、自走して課題を解決できる人間であることを少しでも証明したいと思ったため、今回の実務案件に挑戦させていただきました。 実務経験を積みたいと思い検索する中で、実務の募集を見つけました。どうしても実務経験を積みたいと思い2度も応募フォームを送信し気持ちが届いたのか実務案件に参画させて頂くことになりました。 1. 実装準備 実装を始める前に、初めて聞く用語の理解や、計測ツールを使っての問題の洗い出し方法を理解してから着手しました。まずは実装準備をどの様にしてきたのかを説明していきたいと思います。 Core Web Vitalsとは? Core Web Vitalsの知識が無いと何も始まりませんので実務ではしっかり用語の意味を理解してから着手しました。 ここでは閲覧して頂いている皆様に簡潔にお伝えさせて頂きますm(_ _)m Core Web Vitalsとは ユーザー体験を向上させるためのGoogleが定めたガイダンス 一言で表すとユーザーが感じる使い勝手を評価する指標 Core Web Vitals参考URL https://webtan.impress.co.jp/e/2020/06/05/36210 内容を深ぼると指針が分かりやすく定められていました。 Core Web Vitalsの指針 LCP(Largest Contentful Paint) 読み込みパフォーマンスを測定して数値化したもの。ユーザーがそのWebページのなかで最も有意義なコンテンツをどれだけ早く見ることができるかを表す指標。一言で言うと読み込み時間の事。ページの読み込み開始から2.5秒以内にそのページが描画されることが望ましいとされる。 FID(First Input Delay) ユーザーがWebページを閲覧する際に ページのレスポンスが良いかどうかを測定した数値。 ユーザー体験(UX)の最適化にはこのFIDの数値が 100ミリ/秒未満であることが望ましいとされる。 CLS(Cumulative Layout Shift) ユーザーがページを閲覧する際、 テキストや画像などのコンテンツが快適に表示されているか否かを示す指標。 ユーザー体験(UX)の最適化には、このCLSの値を 0.1未満に維持しなければならない。 参考URL https://mtame.jp/seo/corewebvitals/ 何を改善するのか? Core Web Vitalsの3つの指針を理解したのですが、今回どこの指針の部分を改善するのかを決めないといけません。 Core Web Vitalsの改善方法を調べていくと、ウェブページの読み込み速度をスコア測定してくれるツールである『PageSpeed Insights』というツールをがある事を知りました。ツール内で今回依頼を頂いたサイトURLを入力すると計測してくれます。計測結果で表示された内容で、適切な画像サイズの画像、使用していないJavaScriptの削減、使用していないCSSの削減の3項目が改善の余地があると指摘を受けました。 今回私が施策提案させて頂いたのは、使用していないCSSの削減と、適切な画像サイズの変更の2項目です。 前もって説明させて頂きますが、適切な画像サイズの画像の施策については、実装する事が出来ませんでした、、、 理由は、 指摘のあった画像部分だけを圧縮すればいいのだと施策を勘違いしてしまったこと 使用していないCSSの削減*の施策に時間が掛かってしまったこと の2点です。 こちらに関しては力不足を痛感しました。とても悔しい気持ちでした、、、 しかしこの悔しい気持ちをバネに今後エンジニアとして更に『成長していきたい』と気持ちが強くなりました!! PageSpeed Insights URL https://pagespeed.web.dev/?utm_source=psi&utm_medium=redirect&hl=ja 指摘を受けた内容 適切なサイズの画像 使用していないJavaScriptの削減 使用していないCSSの削減 原因特定 CSS削減の余地があると指摘を受けたのですが、まずは現状どのようにCSSが読み込まれている状況なのか確認しました。すると、assets/stylesheet/apllication.cssに全ファイル読み込まれている記述がありました。 assets/stylesheet/apllication.css @import 'bases/_variable'; @import 'bulma/bulma'; @import 'bases/_base'; @import 'partials/*'; @import 'pages/*'; 更に全ページの共通のレイアウトファイルであるviews/layout/application.html.slimにstylesheet_link_tag 'application'、 media: 'all'で全てのCSSファイルが読み込まれている現状でした。 views/layout/application.html.slim = stylesheet_link_tag 'application' media: 'all' このコードを見てわかったことは、全ページで共通のCSSを読み込んでしまっている事が原因で、ページ表示速度が遅くなっている、ということです。 原因を解決する為の学習 全ページで共通のCSSを読み込んでしまっている事が原因だったのですが、CSS設計の基礎を知らないと解決策の模索が出来ないなと思いました。そこでCSSの事をもっと詳しくなりたいと思い書籍を購入しました。 書籍を読んだ内容を元に今回のCSS設計時に重要となる観点をまとめ、その観点に基づいて最適な設計案を作成しました。目を通して特に重要であると思われる内容を絞り出しました。 参考書籍URL https://www.amazon.co.jp/dp/B0856YMH7L CSSの問題点と原因 CSSの問題点は、ページ数が増えるとCSSもどんどん複雑になり、管理しきれなくなってくる事です。数ページだけのシンプルなサイトであれば大した問題ではありませんが、世の中で使われているWebサイトは、決して小規模なものだけではありません。1ページのみでもWebサイトと呼べますが、上限は限りなく、100ページ、1,000ページ、10,000ページ超のWebサイトも珍しくありません。 そんな状況で何も考えずトップページではp要素を「大きくしたいから、fontsizeを18pxから20pxに変更しよう」とするとどうなるかというと、もちろんトップページだけ見れば意図した通りにp要素は20pxになります。しかしCSSの「スタイリングの内容がCSSファイルを読み込んでいるすべてのページに反映される」という仕様上、他のページのすべてのp要素も20pxになってしまいます。きちんとスタイリングの規則を決めた上でCSSが作成されているのであれば、まだよいですが、規則をきちんと決めていないと、「ページ数が増えると、CSSもどんどん複雑になり、管理しきれなくなってくる」という問題が発生し始めます。 規則のないCSSは、ページ数が10ページを超えただけでもすべてを把握するのは難しいです。そしてWebサイトは、制作して、公開し、終わり、ではありません。その次には情報を更新したり、新たなページを作成したり運用し続けていかないといけません。数ヶ月後の自分が、あるいは他の人が読み解いて、想定外の影響がないように運用していくのは、とても骨が折れる作業です。そしてそんな状態を避けようと規則を定めたとしても、すぐに破綻することがほとんどです。なぜこのようなことが起こってしまうのでしょうか? その原因は「CSSはすべてがグローバルスコープ」です。グローバルスコープとは読み込んだCSSファイル全てが同じスコープで管理されてしまう事。 原因を回避する為のCSS設計とは ではどの様な設計によってCSSを管理し壊れにくくすることができるのかと言うと、具体的には、「予測できる」、「再利用できる」、「保守できる」、「拡張できる」の4つ設計指針がある様です。これはGoogleのエンジニアであるPhilip Walton氏が「CSS Architecture」という記事で提唱した考え方です。 4つのCSS設計指針とは 予測できる スタイリングが期待通りに振る舞うかどうか スタイリングの影響範囲が予測できるか 再利用できる 既存のパーツを別の箇所でも使用したいとき、コードをいちいち書き直したり上書きする手間がない状態 保守できる 新しいモジュールや機能を追加・更新、あるいは配置換えしたとき、既存のCSSをリファクタリングする必要がない状態 拡張できる CSSに携わる人が1人であっても複数からなるチームであっても、問題なく管理できる状態 自分とその他の開発者にとってメンテナンス・管理がしやすくする事 施策提案の設計 上記の4つの設計指針と問題点を元に、現状のクライアントの開発人数が少ないこと、また今後実際に運用するのはクライアントである為、最終的にこのような設計方針で検討する事にしました。 ルールが複雑でない 拡張しやすい 影響範囲がみだりに広すぎない 現状からの修正コストが大きすぎない 何故このように考えたのか ルールが複雑でない 実際に運用し続け、複雑なルールを設定してしまうと今後変更したい時に修正しにくいのでシンプルな設計が必要だと思いました。 拡張しやすい Webサイトは「公開して終わり」ではなく、公開後も運用が続き、その中で既存のページやモジュールに対する変更が発生することも珍しくありません。今後もサービスが大きくなる可能性があると考えたからです。 影響範囲がみだりに広すぎない 影響範囲の広いCSSを修正しようとしても、影響範囲が広いので、どこでレイアウト崩れなどが発生するかわかりません。一度影響範囲の広いコードを書くと、修正と確認の作業コストにずっと悩まされ続けてしまうと考えたのでこの様な方針にしました。 現状からの修正コストが大きすぎない あまりに大きな修正ををしてしまうと依頼の納期に間に合わなくなってご迷惑おかけしてしまうと考え、大きな修正コストは掛けられないと判断しました。 実装方法を模索 設計指針に基づいて、分かりやすく複雑でなく、現状からの修正が大きすぎないというテーマを元にCSSの設計について調べてみました。調べてみるとファイル分割に焦点が当てられている記事を見つけました。 参考記事の内容 ViewファイルとCSSファイルを1対1になるようにファイルパスを揃えて、スコープ用にdata属性を書きます。修正したいViewファイルがあった場合、対となるCSSファイルを修正するだけで他のページに影響を与えず修正出来るのでとても管理しやすくなります。 次の手順で、各ページで必要なCSSファイルだけを呼び出す Viewファイルごとのルート要素に、自身の app/views/ 以降のファイルパス(拡張子抜き)を data-scope-path というデータ属性で設定。 対となる CSS 側では [data-scope-path="..."] という属性セレクタで絞って参照。 ファイルはapp/assets/stylesheets/エントリー名/scopesに格納。 参考記事URL https://tech.basicinc.jp/articles/166 何故この案にしようとしたのか この記事を読んでいくと、 設計を理解しているメンバーがプロジェクトから離れるとCSSの影響範囲を把握するのが大変です。CSS設計の場合、グローバルスコープなので全ページ確認しないといけないという大変な作業が待っています。新しいプロパティを追加する事によって、他のページのスタイルを崩してしまわないかと、複数のページを確認しなければなりません。どれだけCSSが得意な人がプロジェクトに入ったところで、解決は大変です。 とありました。 今後設計を理解しているメンバーが離れる事もあると思います。前任の方がプロジェクトから離れ新しいメンバーがプロジェクトに参加し、新規にプロパティを追加した事によってどこのページに影響しているのか把握出来ず、複数のページを確認しないといけない確認作業が大変になってしまいます。 そこでViewファイルとCSSファイルを1対1になる様に設計しておけば『このページはこのCSSファイルを変更するだけでOK 』と分かりやすいので、メンバーの入れ替わりがあっても管理しやすいのではと考えました。 この方法を使用し、全ページで同時に読み込まれているCSSファイル分割してページ毎にCSSファイルを読み込ませたら、分かりやすく、管理しやすいのではと思いこの案で実装していこうと思いました。 実装方針の提案書 今回の案件に対しクライアントに以下の様に提案書を提出しました。 CSSの設計方針 viewsの各ファイルに対して、以下方針で対応するCSSファイルを作成します ページファイル: CSSと1:1対応させる パーシャルファイル CSSと1:多対応させる 施策提案書を提出し確認を取る 実装方法を確認したところ、大まかな流れですが承諾頂けました。 一つクライアントから、『ページ単位での分割は実装/移行コストが少し高いように感じるのでコントローラー単位での分割に切り替えて欲しい』という依頼を頂きました。 私が提案した内容は、コントローラ単位での分割にも応用出来る内容でした。そこで開発方針を「ページ単位での分割」から「コントローラー単位での分割」に切り替えました。実装方針を固める事が出来たので本格的に実装開始する事にしました。 まず最初にユーザーがアクセスし、表示されるトップページで使用されるコントローラーから施策をスタートしました。順次流入数が多いページで使われるコントローラーの順番で進めていく方向で決まりました。施策内容と施策順序の許可を頂けましたので、ここから実際に手を動かし実装開始となります。 2. 実装開始 まずは全ページ読み込まれてしまっているのでコントローラー毎に必要なCSSファイルを読み込めるようにする為の基盤作りから進めていきました。 全ページの共通のレイアウトファイルであるviews/layout/application.html.slimに対し 1. if文の条件分岐で値が入っていたら個別のCSSを読み込み、なかったら既存のapplication.cssを読み込む stylesheet_link_tag yield(:stylesheet_scope)でビューテンプレートに対応するCSSファイルを呼び出す views/layout/application.html.slim - if content_for?(:stylesheet_scope) = stylesheet_link_tag yield(:stylesheet_scope), media: 'all' - else = stylesheet_link_tag 'application', media: 'all' 2. 設定したいコントローラで扱われるビューファイル全部にvirtual_pathを指定 各ページテンプレートの最上位の要素に記載 app/views/hoge/hoge.html.slim - stylesheet_scope(@virtual_path) 3. メソッドを定義しスコープを受け取れるようにする app/helpers/application_helper.rb STYLESHEET_PATH_PATTERN = /^(?<controller>[a-z_]+?)\/(?<method>[a-z_]*)$/ def stylesheet_scope(path) controller_name = path.match(STYLESHEET_PATH_PATTERN)&.[](:controller) content_for :stylesheet_scope, controller_name end 4. app/assets/stylesheets/直下にCSSファイル作成 コントローラー名の CSSファイルを作成し、コントローラで使われているビューファイルのCSSを全て読み込ませる app/assets/stylesheets/hoge.css @import 'bases/_variable'; @import 'bulma/bulma'; @import 'bases/_base'; @import 'partials/_footer'; @import 'partials/_header'; @import 'pages/_hoge'; @import 'partials/_fuga'; 3. 実装結果 数値を確認した結果、ページの読み込みスピード改善ができていました。 下記の検証手順で確認してみると、現在読み込まれているCSSファイルが確認できます。 今後サービスが大きくなり別の方がアサインされた場合でも拡張しやすく、新しいモジュールや機能を追加し、配置換えしたときでも保守性も保たれると思います。特にコントローラーとCSSファイルを1:1にする事によってどこのCSSファイルを読み込んでいるか分かりやすくなったと思い、今後の運用しやすくする為の基盤が作られたと思います。 個別読み込みと読み込みスピード改善の検証方法 Coverage機能による検証手順 Google Chromeの検証ツールからCoverage機能を使用 a. 「Coverage」という機能を使用するのですが、標準では表示されていないので初回は追加設定します。 b. 「開発者ツール」右上に表示されている「…」が縦に並んだ設定ボタンをクリックします。 c. 「More tools」から「Coverage」を選択し追加します。 d. 「Coverage」が追加されると、開発者ツールの下に表示されます。 Coverageで表示されるURLを確認 a. 対象ページ:設定したCSSファイルが読み込まれている b. 対象外のページ:application.cssが読み込まれている 読み込みスピードの改善数字 a. Before: LCP 1.5秒 b. After : LCP 0.8秒 計測結果と確認一覧 実装後にPageSpeed Insightsでページ読み込みスピードが改善されているか数値確認し、Coverage機能を使い現在読み込まれているCSSファイルの確認をしました。 確認項目 読み込み箇所の確認は以下の通りです。 個別読み込みを設定し必要なCSSが読み込まれている 個別で設定したCSSファイルだけがしっかり読み込まれています。 個別読み込みを設定していないので全ファイルのCSSが読み込まれている @import 'pages/*':のようにディレクトリ内の全ファイルが読み込まれています。 計測結果 計測結果は以下の通りです。 コントローラ毎に個別のCSSが読み込まれている状態の計測数値 LCPの計測値が0.8秒と基準値より上回っています。 個別読み込みを設定していない状態の計測数値 LCPの計測値が1.5秒と基準値より下回っています。 実務を始める前と終えた後の変化 実務経験前と経験後でプログラミングに対しての考え方や取り組み方が大きく変わりました。 具体的に3つです。 1. エンジニアとしての心構え 実務を経験する前は書いてある事をそのままやる事しかしてこなかったと思います。教材があり、そこに書いてある事をそのままやるだけで自分で考えて取り組んでいませんでした。 今回実務体験をさせていただき、課題に対してどうやって解決出来るか思考し、それに対しての解決策を導き出す大切さを知る事ができました。書いてある事をそのままやるのではなく自分で思考して取り組む大切さを知る事ができました。 2. コードに対しての品質の意識 これは本当に反省です。最も意識が変わった事だと思います。実務経験前の私は動けば大丈夫くらいな気持ちでコードを書いていました。 コード品質の良し悪しがサービスの良し悪しにも直結します。しっかり理解したコードを書き、他の人が見ても分かる様なコードを書き品質を落とさない大切さを知る事ができました。 3. 相手が言っていることを理解する力と伝え方 仕事は基本相手がいて成立するものなので、自分から提案する力、正確に伝える文章力、相手が本当に必要としていることを汲み取る力が大切だと気づけました。 今回の実務を終えて一つ毎日続けようかなと思うことが一つあります。 それは読書です。 元々読書はしていたのですが今後はアウトプット前提の読書をしていきます。 アウトプットとは読書をしたら感想文を書くという事です。感想文を書くという事は、何となく読書してたら感想は書けないので集中して読む+作者の意図を正確に理解する力がついていくと思います。今後エンジニアとして働くときに相手の言っている事の理解と背景を汲み取る力をつける事によって、一番良い結果をクライアントやお願いしてくれたメンバーにお返しが出来ると思いました。ビジネスマンとして成長する為に、今後『読解力』と、『ロジカルシンキング』の二つの力を付けていきます。 実務を終えての感想とプログラミングに対しての思い 今回の経験を踏まえて、自分に改めて足りていないもの、学習しなければならないこと、エンジニアという職業がコードを書くだけの職業ではないこと、技術以外に必要となるスキル、エンジニアになるってどんなことなのか等、本当に多くのことを学ばせていただきました。 実務を通し、改めてエンジニアという職業の魅力を感じました。エンジニアの技術と現職の楽器の演奏技術は似ているなと改めて感じました。 僕は音楽講師を目指した時も技術面に関して何度もつまずき悩まされた事がありました。 そんな僕でも自分の弱点をしっかり分析し一つずつ解消する事によって講師テストに合格する事ができました。 今回も状況がよく似ていると思っています。 エンジニアという立場で仕事をするとは、音楽に置き換えるとライブ出演したような感覚でした。 最初は周りに迷惑をかけてしまう事があると思いますが、誰よりも情熱を持って取り組んでたらその差は埋まると思っています。 エンジニア転職した後も、一つ一つ毎日技術や知識を積み上げて成長し、会社で必要とされ、将来リーダー的なポジションを目指し、メンバーを引っ張っていける様なエンジニアを目指して行きたいと思います。 今回リーダーのY氏に褒められた部分が明るく、ポジティブなのでチームに居るとチームが明るくなると言っていただけました。とても嬉しかったです。技術面では未熟ですが、エンジニアではチーム開発する時の雰囲気作りも大切なんだなと学べました。将来的にチーム開発に携わる時は雰囲気作りのサポートもしていきたいです。それと僕は教える事は仕事柄得意な方だと思うので、新人教育などにも携わっていきたいなと思い、段階的に各ポジションに就いてみたいと思っています。 学習が大変という意見もありますが、好きな事を「学習する」という考えは、違和感があります。 僕は楽器の練習を練習と思ってなく、好きな事だから自然と追求するものだと思っています。 プログラミングに対しても同じ思いでいます。まだ分からない事だらけで自然に追求していけるレベルまで到底達していませんが、そのレベルまでいったら僕は凝り性なのでどんどん追求できる自信があります。 そして何より感謝したいのは、リーダであるY氏と、クライアントです。 私に何度もコードレビューや、施策提案の進め方を丁寧に指導していただく事によって、今回の実務をやり遂げる事ができました。まだまだ自分の力だけでは出来ていないことばかりですが、今後しっかり成長し、あの時育ててよかったなと思ってもらえる様なエンジニアに成長していきます。 同じミスをしない対策としては、読解力を鍛え、何をして欲しいか理解し、技術+知識+理解力を付けて同じミスをしない様にしていきます。 一緒に実務に挑戦した仲間たちにもとても感謝しています。大変な時に励まし合える仲間がいる事は本当に素晴らしいなと実感しました。 エンジニアとしてまだまだ至らない部分が多くありますが、学習を継続するとともに、今後の転職活動に力を入れたいと思います。ご覧いただきありがとうございました。