初めて記事を書いていみました。
外国人かつ日本語学科ではないので、日本語がグダグダかもしれませんが、大目に見ていただければ幸いです。
コメント、指摘などはご自由にどうぞ。
※この記事には前編と後編があります。slick.jsは後編にて紹介します。
本記事対象
本記事は本人のキャリア経験を踏まえ、下記の方を対象に想定し、作成いたしました。- Javascript初心者
- フロントエンド初心者
- 非IT系会社の社内SE、情報システム部
- サイト保守会社との窓口担当者
- ウェブサイトデザインに興味のある方
環境
会社の公式サイトが2013年にウェブサイト制作会社に依頼し、多分値引きをしたか、フレームワークを使わず、バックエンドのPHPはバニラ、フロントエンドも全部バニラで開発されました。
PHPは「Template Power」(Open Power Template)というテンプレートエンジンと「Smarty」テンプレートエンジンを使用しており、PHPとHTMLは一応分離できております。
というものの、バニラのPHPという事実は変わりありません。
開発保守会社との契約が2021年2月に中止となったので、現在は社内情報システム部で保守運用を行なっております。
最後開発保守会社に依頼した案件は2020年のサーバーをGoogle Cloud Plaformに移動し、モバイル決済を実装するという案件でした。
よって、サイトの保守はGCPと「cPanel」にて行っています。
PHPはcPanelに合わせ、最低でも7.2バージョンを使用しており、データベースはMySQLです。
その他はセキュリティな為公開できないので、ご了承ください。
背景
当時サイトのデザインを担当した上司が、公式サイトの見た目を全体的に更新したいです。他のウェブ制作会社は「既存サイトの引継ぎ、追加開発及び保守」という形式を見積もった値段が、「新規サイトの開発及び保守」を見積もった値段と同じくらいだったが、社長は値引きを要求し、交渉した末、破局する羽目になりました。
以降、新規サイトの立ち上げ案件は外注しているCTOの会社が引き取りました。
上司はカドカワ様のようなバナーが、どのようなビジュアル効果があるのかを知りたいそうです。
新規サイトはまだ開発初期段階なので、こっそり既存のサイトに実装してみました。
(実は新規サイトで使う技術を勉強すべきだけど)
目標
元のバナーをカドカワ様のバナーのようにデザイン変更します。 ビフォーは下記の画像のようになっていました。目標のバナースタイルはカドカワ様の公式サイトをご参照ください。
https://www.kadokawa.co.jp/
過程
フレームワークなし、長年に渡ってウェブ会社に少ない保守費用しか払っていなくて、ウェブ会社も保守担当されているエンジニアが何人も変わったので、コード自体は悪夢的に多彩多様です。おまけに要件定義書などの技術書類が存在していないそうです。(先輩の話によりますと、要件定義書を作るには追加料金が掛かるそうです)
よって、最初はバニラで改修したいと思いましたが、満足できるバナーを作れず、そして、バニラだからこそ、さらにコードの乱雑度を増すということになります。
最後は何度もカドカワ様、スリーエーネットワーク様のサイトを見て、両方ともHTMLタグのclassに「slick」というキーワードがあると気づいて、slick.jsというJqueryのプラグインに辿り着きました。
参考にならない自作の駄作バナー
バックエンドには関係ないですから、HTML、CSS、Javascriptのみ解説します。- まずはバナーの画像たち
<{section name=adv01_sec1 loop=$a01_link01_1}>
<div class="card other">
<{if $a01_link01_1[adv01_sec1].weblink == ""}>
<a href="javascript:void(0);" title="<{$a01_link01_1[adv01_sec1].title01}>">
<img src="<{$a01_link01path}><{$a01_link01_1[adv01_sec1].dirname}>Crop_<{$a01_link01_1[adv01_sec1].upfile1}>" width="687" height="239" />
</a>
<{else}>
<a href="<{$a01_link01_1[adv01_sec1].weblink}>" target="<{$a01_link01_1[adv01_sec1].weblink_target}>" title="<{$a01_link01_1[adv01_sec1].title01}>">
<img src="<{$a01_link01path}><{$a01_link01_1[adv01_sec1].dirname}>Crop_<{$a01_link01_1[adv01_sec1].upfile1}>" width="687" height="239" />
</a>
<{/if}>
</div>
<{/section}>
結構雑ですよね。
<{XXXXX}>
というのはPHPのSmartyの記述です。(2022/09/01訂正しました。開発会社は<{}>
でSmartyを定義しているそうです。下記Open Power Templateを全部Smartyに修正します)
sectionというのは、PHPがHTMLに投げてきたArrayのものを全部表示するように。
よくあるfor($i=0; $i<count(ArrayName); $i++)
と考えれば良いと思います。
if else はお馴染みのほぼ全言語がある条件分岐の記述です。
Smartyのif else は閉じタグの<{/if}>
が必要です。
弊社サイトのCMSにて、もしバナー画像にURLが設定していない場合はif区画、設定している場合はelse区画となります。
これでデータベースにどれだけのバナー画像があっても、動的に自動生成できます。
- そして、これらを右左のボタンと一緒にwrapperに包んで、他の
div
と調整しやすいようにします。
<div class="bannerDiv_fake" oncontextmenu="return false;">
<div class="cardWrapper" id="cardWrapper">
<div class="card other">
先ほどのあれら
</div>
</div>
<div class="leftBtn" onclick="prevPic()">❮</div>
<div class="rightBtn" onclick="nextPic()">❯</div>
</div>
- 最後は「・・・・・」の
div
を追加します。こちらもTemplatePowerで動的に生成することが必要です。
<div class="indexBtns">
<{section name=adv01_sec1 loop=$a01_link01_1}>
<span class="normal" onclick="movePic(<{$a01_link01_1[adv01_sec1].count}>)">
<{$a01_link01_1[adv01_sec1].count+1}>
</span>
<{/section}>
</div>
別々にしたのは、CSS上調整しやすいようにしたので、別に「・・・・・」がbannerのwapper内にいても良いです。
こちらは個人の習慣、技術力、チームなど色々と相談してください。
上司は「・・・・・」の中に数字を表示したいので、span
の中身も文字を入れました。
- ちなみに、CSSはこのようになっています。上司のデザインに合わせて作成したものですので、ご都合に合わせて調整してください。
.bannerDiv {
height: auto;
width: 100%;
margin: auto;
margin-top: -10px;
margin-bottom: 10px;
overflow: hidden;
position: relative;
display: flex;
justify-content: start;
align-items: center;
user-select: none;
}
.bannerDiv .cardWrapper {
display: flex;
justify-content: end;
align-items: center;
transition: 0.5s all;
}
.bannerDiv .card{
min-width: 687px;
border: 1px solid lightgrey;
border-left: 0.5px solid lightgrey;
border-right: 0.5px solid lightgrey;
height: auto;
transition: 0.3s all;
position: relative;
}
.bannerDiv .card:hover {
opacity: 70%;
}
.bannerDiv .leftBtn {
position: absolute;
left: 0;
background: rgba(0,0,0,0.5);
height: 100%;
width: 50px;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
color: rgba(255,255,255,0.4);
transition: 0.3s all;
cursor: pointer;
user-select: none;
}
.bannerDiv .leftBtn:hover, .bannerDiv .rightBtn:hover {
background: rgba(0,0,0,0.85);
color: rgba(255,255,255,1.0);
}
.bannerDiv .rightBtn {
position: absolute;
background: rgba(0,0,0,0.4);
height: 100%;
width: 50px;
right: 0;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
color: rgba(255,255,255,0.5);
transition: 0.3s all;
cursor: pointer;
user-select: none;
}
.bannerDiv .first {
position: relative;
left: 50VW;
transform: translate(-50%, 0);
}
.bannerDiv .fakeFirst {
position: relative;
left: 50VW;
transform: translate(-50%, 0);
}
.bannerDiv .other {
position: relative;
left: 50VW;
transform: translate(-50%, 0);
}
.bannerDiv .fakeLast {
position: relative;
left: 50VW;
transform: translate(-50%, 0);
}
.bannerDiv .cardWrapper {
margin-left: -688px;
}
.indexBtns {
position: relative;
margin: auto;
background: none;
margin-top: -10px;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
}
.indexBtns span {
height: 16px;
width: 16px;
border-radius: 50%;
cursor: pointer;
border: none;
margin: 4px 3px 4px 3px;
display: flex;
justify-content: center;
align-items: center;
font-size: 0.5px;
color: white;
font-family: Arial;
}
.indexBtns span.normal {
background: lightgrey;
}
.indexBtns span.active {
background: grey;
}
.indexBtns button {
background: none;
border: 0.5px solid;
border-radius: 3px;
font-size: 12px;
margin-left: 2px;
cursor: pointer;
}
.indexBtns .playing {
border-color: green;
color: green;
}
.indexBtns .pausing {
border-color: red;
color: red;
}
.bannerDiv_fake {
border: 0px solid black;
height: auto;
width: 100%;
margin: auto;
margin-top: -10px;
margin-bottom: 10px;
overflow: hidden;
position: relative;
display: flex;
justify-content: start;
align-items: center;
}
.bannerDiv_fake .cardWrapper {
display: flex;
justify-content: end;
align-items: center;
transition: 0.5s all;
}
.bannerDiv_fake .card {
min-width: 687px;
border: 1px solid lightgrey;
border-left: 0.5px solid lightgrey;
border-right: 0.5px solid lightgrey;
height: auto;
transition: 0.3s all;
position: relative;
}
.bannerDiv_fake .cardWrapper .banner1 {
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: row;
transition: 0.5s all;
z-index: 1;
}
.bannerDiv_fake .cardWrapper .banner2 {
position: absolute;
display: none;
justify-content: flex-start;
align-items: center;
flex-direction: row;
transition: 0.5s all;
z-index: 0;
}
.bannerDiv_fake .first {
position: relative;
left: 50VW;
transform: translate(-50%, 0);
}
.bannerDiv_fake .fakeFirst {
position: relative;
left: 50VW;
transform: translate(-50%, 0);
}
.bannerDiv_fake .other {
position: relative;
left: 50VW;
transform: translate(-50%, 0);
}
.bannerDiv_fake .fakeLast {
position: relative;
left: 50VW;
transform: translate(-50%, 0);
}
.bannerDiv_fake .cardWrapper {
margin-left: -688px;
}
.bannerDiv_fake .card:hover {
opacity: 70%;
}
.bannerDiv_fake .leftBtn{
position: absolute;
left: 0;
background: rgba(0,0,0,0.5);
height: 100%;
width: 50px;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
color: rgba(255,255,255,0.4);
transition: 0.3s all;
cursor: pointer;
user-select: none;
z-index: 10;
}
.bannerDiv_fake .leftBtn:hover, .bannerDiv_fake .rightBtn:hover {
background: rgba(0,0,0,0.85);
color: rgba(255,255,255,1.0);
}
.bannerDiv_fake .rightBtn{
position: absolute;
background: rgba(0,0,0,0.4);
height: 100%;
width: 50px;
right: 0;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
color: rgba(255,255,255,0.5);
transition: 0.3s all;
cursor: pointer;
user-select: none;
z-index: 10;
}
あまり参考にならないですよね。ザ・初心者って感じです。
補足となりますが、弊社サイトのCSSは上記のようになっています。
多分、SCSSなど、今流行りのCSSフレームワークも使っていなかったと思います。
bannerDiv_fake
とbannerDiv
は同じですが、前者はテスト時に使っているものなんです。
そのまま公式サイトにコードを残して、完全に悪い癖ですね。
一つのサイトがもし、誰かが修正して、テストに使ったものも消さずに残って、何人も何代も何年も経てば、コードが雑乱になって、保守にコード読みに使う時間が何倍も増えて、効率悪くなります。
自分もまだ初心者で、自分が言うのもあれなんですが、テスト用のコードは消しましょう。
綺麗にコードを保たないと、弊社のように、「新しいウェブ開発保守会社の見積もりが高額になっていって、新規サイトの立ち上げと同じ値段になる」ことになりますので、お気をつけください。
(それに1ヶ月経てば、自分がなんで書いたかを忘れ、自分が転職したら後任の者が保守しづらくなります)
- 最後はJavascriptの部分です。初学者の最大の仲間であるW3Schoolsを主に参考しています。
使う変数は下記のようになっています。
var cardIndex = 0;
let cardWidth = 688;
let cards = document.getElementsByClassName("card");//因為有複製兩張皆在頭尾的,所以要扣掉兩個長度
let cardWrapper = document.getElementById("cardWrapper");
let cardBtns = document.getElementsByClassName("normal");
cardBtns[0].classList.add("active");
cardIndexは現在どの画像を真ん中に表示するかを指します。
cardWidthはバナーの画像の一枚の長辺です。
cardsはバナーは何枚画像があるか、cardWrapperはバナーそのもの、cardBtnsは「・・・」の数。
- 画像移動の関数はこちらです。
function movePic(index){
cardWrapper.style.transform = "translate( "+ -cardWidth*index +"px, 0)";
cardIndex = index;
}
ただwrapperを左に移動するだけです。左に移動するには数値をマイナスしないといけません。
一枚一枚で、cardIndexに合わせて移動します。
ちなみに、「・・・」のspanタグには生成の際に「onclick」のeventを加え付けてあったので、そのまま「・・・」を押したら、指定画像と位置に移動できます。
- 左、右の矢印ボタンの関数
function prevPic(){
cardIndex--;
if(cardIndex < 0 ){
cardIndex = cards.length-3;
}
movePic(cardIndex);
}
function nextPic(){
cardIndex++;
if(cardIndex >= cards.length-2){
cardIndex = 0;
}
movePic(cardIndex);
}
クリックしたら、cardIndexがまず変動し、それからカード移動の関数を実行します。
そして、重要なのはcardIndexが最大値以上、最小値以下、つまり一枚目より前、最後の画像より後、となった場合の処理です。
最初と最後の前には「仮の画像」があります。
仮の画像がないと、最初の画像と最後の画像が真ん中になったとき、前後が空っぽになるので、「仮の画像」を作らないと可笑しく見えます。
なので、cardIndexの限界値はcardsの数を仮の画像の数を引かないといけません。
- 「・・・」のアクティブclassを付ける関数
function activeBtnColor(index){
for(let i=0; i<cardBtns.length; i++){
cardBtns[i].classList.remove("active");
}
cardBtns[index].classList.add("active");
}
こちらは完全にW3Schoolsと同じ、点の要素を全部選択して、activeのclassを全部外してから、
指定の点にactiveを付けるという簡単なロジックです。
この関数を左右矢印の関数、画像移動の関数にも呼んだら、画像移動は点のactiveと連携できます。下記のようになります。
function movePic(index){
cardWrapper.style.transform = "translate( "+ -cardWidth*index +"px, 0)";
activeBtnColor(index);
cardIndex = index;
}
function prevPic(){
cardIndex--;
if(cardIndex < 0 ){
cardIndex = cards.length-3;
}
movePic(cardIndex);
activeBtnColor(cardIndex);
}
function nextPic(){
cardIndex++;
if(cardIndex >= cards.length-2){
cardIndex = 0;
}
movePic(cardIndex);
activeBtnColor(cardIndex);
}
完成まであっと一歩、「自動的に動く」です。
時間の設定と言ったら、setInterval
とsetTimeout
が先に思い浮かびます。
setInterval
を使いました。大雑把なコードは下記のようになっています。
let play;
clearInterval(play);
play = setInterval( function(){
nextPic();
}, 3000);
これを一つの関数に包みたいんですが、もし関数に包んだら、play変数が違うものになって、消去できなくなり、
ボタンを押すたびに新しいsetInterval
を追加していく感じになっていましたので、全部上記のように記述を何度も重複書いていました。
(それともただ自分のプログラミングの実力不足で、そのような状態に陥ったのか)
そして、下記の幾つの状態で、自動的に動くを条件付けます。
- このページに入った際に、自動的に動く
- マウスが画像の上にいる時、自動的に動かない
- マウスが画像から離れたとき、自動的に動く
- マウスが点の上にいる時、自動的に動かない
- マウスが左右矢印を押すと、自動的に動く
- 1、2、3は全域変数、
script
タグのところに書くと良いでしょう。(良くないです)
let play;
clearInterval(play);
play = setInterval( function(){
nextPic();
}, 3000);
cardWrapper.addEventListener("mouseover", () => {
clearInterval(play);
});
cardWrapper.addEventListener("mouseout", () => {
play = setInterval( function(){
nextPic();
}, 3000);
});
サイトにアクセスする際に、まずplay変数のsetIntervalを消去します。
もし変にsetIntervalがあるなら、二個目のsetIntervalを加えると、バナーがとんでもないスピードで転換していきますから、念のためです。
そしてmouseover
とmouseout
のeventをバナー区域に追加しました。
- 4、5は関数の中に加えつけます。
function movePic(index){
cardWrapper.style.transform = "translate( "+ -cardWidth*index +"px, 0)";
activeBtnColor(index);
cardIndex = index;
clearInterval(play);
}
function prevPic(){
cardIndex--;
if(cardIndex < 0 ){
cardIndex = cards.length-3;
}
movePic(cardIndex);
activeBtnColor(cardIndex);
clearInterval(play);
play = setInterval( function(){
nextPic();
}, 3000);
}
function nextPic(){
cardIndex++;
if(cardIndex >= cards.length-2){
cardIndex = 0;
}
movePic(cardIndex);
activeBtnColor(cardIndex);
clearInterval(play);
play = setInterval( function(){
nextPic();
}, 3000);
}
めちゃ長くなりましたね。一応、これで正常に動けるはずだ...と思いきや、カドカワ様のバナーには「マウスでドラッグして、画像を移動できる」機能があります。
また、カドカワ様のバナーは、最後の画像から一枚目の画像に変換するとき、めちゃくちゃ移動するわけではありません。
(上記のコードだと、一番後ろから、何千ピクセルも移動して、一番前の画像になリます。
仮の画像があるにも関わらず、変に大移動してしまいます)
それを調整するのがまた一苦労をします。ここまで既に二日間の勤務時間を費やしましたので、とても効率的とは思えません。
なので、後編はslick.jsを使って、簡単にカドカワ様のようなバナーを作れることを紹介します。
(後編へ続く)
参考資料
- https://www.gushiciku.cn/pl/pPs5/zh-hk
- https://www.w3schools.com/howto/howto_js_slideshow.asp
- https://kenwheeler.github.io/slick/
- https://www.kadokawa.co.jp/
- https://www.3anet.co.jp/