javascript(jquery)を使って円形のスライダー(circleslider)を自作したので、コードを掲載し記事にしておきたいと思います。360°で回転し入れ替わる円形のスライダーと普通のスライダーカルーセルを組み合わせてみました。ランディングページやプロモーションサイトなどでご使用して頂ければと思います。制作する過程でTiny Circlesliderを参考にさせて頂きました。
完成形は以下のようになります。
*CYBER JAPAN関係者様。画像の仕様等問題があればご連絡下さい。すぐに差し替えます。
js circle sider 目次
- circleslider javascriptとhtml全コード
- slider用ドキュメントをcreateDocumentFragment()を使用して実装しする
- circleslider(円形スライダー)実装する
- slider carouselを実装しcirclesliderと同期させる
- circlesliderのスタイリングをcssで設定する
- circlesliderのレスポンシブ対応について
circleslider javascriptとhtml全コード
始めに円形sliderのjavascriptとhtmlをすべて提示して少しずつ抜き取りながら解説していきたいと思います。
circlesiderの全コードは以下になります。
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
/* * Plugin_name:Ak_CircleSlider Author:Aturow Komori Author URI: https://over-creation-studio.jp/ Version: 1.0.1 License: Freeeeeeee < Please give me a job license > * */ $(document).ready(function () { $("#area").ak_cslider_plug({intervalTime: 3000}); }); (function ($) { /* * start plugin */ $.fn.ak_cslider_plug = function (options) { /* * settings */ var settings = $.extend({ intervalTime: 5000, start: 0, slide_start: 0, }, options); var self = this; var docFragment = document.createDocumentFragment(); var width = self.innerWidth() //selector width , height = self.innerHeight() //selector height , radius = width / 2 //selector 半径 , dot_dia = 2.5 * width / 5 //円形スライダー一つの円の直径 , centerX = radius - dot_dia / 2 //slector(#area)の中心座標 , centerY = radius - dot_dia / 2 , dots = [] , angle_start = [] , allimg = ['images/asami.jpg', 'images/chiaki.jpg', 'images/haruka.jpg', 'images/junon.jpg', 'images/mirin.jpg'] , alltxt = ['ASAMI', 'CHIAKI', 'HARUKA', 'JUNON', 'MIRIN'] , classname = ['viewport', 'round', 'thum'] , slidesAngle = 360 / allimg.length //circlesleder 1回の移動半径 , slideCurrent = 0 , $slides; position = { 'width': width, 'height': height }; self.css(position); settings.main_dio = 200; /* * create document */ function makeClone() { $.each(classname, function () { if (this.length > 0) { //要素の生成 div = document.createElement('div'); div.className = this; if (this == 'viewport') { ul = document.createElement('ul'); ul.className = "overview"; drawCreates(ul); div.appendChild(ul); } ; if (this == 'thum') { var d = setDots(); div.appendChild(d); } ; docFragment.appendChild(div.cloneNode(true)); } }); self.append(docFragment); $slides = $('.overview').children(); $('.overview').append($slides.first().clone()); position = { 'width': width + dot_dia / 2, //round直径 'height': height + dot_dia / 2, 'top': -(dot_dia / 4), //round top position 'left': -(dot_dia / 4), //round left position }; $('.round').css(position); position = { 'width': width, 'height': height }; $('.overview li').css(position); return self; } function drawCreates(selector) { //sliderカルーセル画像配置 var fragment = document.createDocumentFragment(); // それぞれの要素の生成 var list = document.createElement('li'); var image = document.createElement('img'); $.each(allimg, function () { if ($(this).length > 0) { image.setAttribute('src', this); list.appendChild(image); fragment.appendChild(list.cloneNode(true)); } }); selector.appendChild(fragment); } //circleslider それぞれの設定 function setDots() { var fragment = document.createDocumentFragment(); $.each(allimg, function (index) { var div = document.createElement('div'); var txt = alltxt[index]; div.className = 'dot_' + index; //円形スライダーそれぞれにテキストを設定 $(div).html('<h3>' + txt + '</h3>'); //円形スライダーのひとつひとつの座標(x,y)を取得しtop,leftにpositionを設定 var angle = -((slidesAngle * index) + 180) % 360; var top = (Math.cos(Math.PI / 180 * angle) * (radius * 1.25) + centerX); var left = (Math.sin(Math.PI / 180 * angle) * (radius * 1.25) + centerY); position = { '-webkit-transform': 'matrix(1, 0, 0, 1, ' + left + ',' + top + ')', 'width': dot_dia, 'height': dot_dia }; $(div).css(position); //フラグメントにcirclesliderのドキュメントクローンを設定 fragment.appendChild(div.cloneNode(true)); //dotsとangle_start配列にそれぞれのデータをプッシュ dots.push({ "index": index, "angle": angle, "top": top, "left": left, "dot": div.cloneNode(true) }); angle_start.push({ "index": index, "angle": angle, "top": top, "left": left, "dot": div.cloneNode(true) }); }); return fragment; } /* * circle slider */ function circle_slide() { //circleslider 個々を1°移動 set_timer()でslideAngle°まで移動 $.each($('.thum div'), function (index) { var i; i = -(angle_start[index].angle - settings.start) % 360; var top_a = (Math.cos(Math.PI / 180 * i) * (radius * 1.25) + centerX); var left_a = (Math.sin(Math.PI / 180 * i) * (radius * 1.25) + centerY); //circleslider 個々の配置位置設定 $(this).css('transform', 'matrix(1, 0, 0, 1, ' + left_a + ',' + top_a + ')'); angle_start[index].angle++; }); } /* * slider carousel */ function slide() { var j = settings.start; var s = (Math.ceil((settings.slide_start) / 10) * 10) % (width * ($slides.length)); //console.log(s); $('.overview').css({'transform': 'translateX(' + -s + 'px)'}); settings.slide_start += (width / slidesAngle); if (settings.slide_start >= width * j) { settings.slide_start = width * j; } } function move(index) { var slidesTotal = allimg.length; var slideIndex = (slidesTotal + index) % slidesTotal; slideCurrent = slideIndex; var slide = $('.thum div'); slide.eq(slideCurrent).addClass('current'); slide.eq((slideCurrent - 1) % slidesTotal).removeClass('current'); } function set_timer() { var j = 0; var loopFunc = function () { circle_slide(settings.start); slide(); j += 1; if (slidesAngle >= j) { setTimeout(function () { loopFunc(); }, 10); } }; loopFunc(); settings.start++; move(settings.start); //console.log(settings.start); } function start_timer() { set_timer(); this.timer = setTimeout(function () { start_timer(); }, settings.intervalTime); } function firstFunction() { makeClone(); } function secondFunction() { firstFunction(); start_timer(); } function _initialize() { secondFunction(); } _initialize(); return this; }; })(jQuery); |
html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Ak Circle Slider: A lightweight cross browser circular carousel. | 茅ヶ崎、藤沢、平塚のホームページ制作会社 OVER</title> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script src="ak_circle_slider.js" type="text/javascript"></script> <link href="ak_circle_slider.css" rel="stylesheet" type="text/css"/> <link href="https://fonts.googleapis.com/css2?family=Anton&family=Romanesco&display=swap" rel="stylesheet"> </head> <body> <h1>CYBER JAPAN</h1> <div id="area"></div> <div id="start_btn"></div> <div id="stop_btn"></div> </body> </html> |
slider用ドキュメントをcreateDocumentFragment()を使用して実装する
提示したhtml文書を確認して頂くとネイティブの状態だと#area以外は記述されていません。
この中にcirclesliderで必要な要素をjavascriptのcreateDocumentFragment()を使用して追加していきます。
createDocumentFragment()を使用して形成されるDOMノードは小さなDOMツリーの断片としてメモリー上に置かれ、本来のDOMツリーの一部にはならないため、ページ全体のリフロー(reflow : 要素の位置と構造の計算)を発生させないようです。結果パフォーマンスの向上に繋がりやすくなるようです。
今回作成するsliderやjavascriptゲームなどDOMを頻繁に呼び続けるようなケースでは、何をするかにもよりますが使用した方が良いケースが多いと思います。
先に提示したすべてのcirclesliderのjavascriptコードからdocument形成に必要なjavascriptを一部抜きっとって示します。
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
/* * Plugin_name:Ak_CircleSlider Author:Aturow Komori Author URI: https://over-creation-studio.jp/ Version: 1.0.1 License: Freeeeeeee < Please give me a job license > * */ $(document).ready(function () { $("#area").ak_cslider_plug({intervalTime: 3000}); }); (function ($) { /* * start plugin */ $.fn.ak_cslider_plug = function (options) { /* * settings */ var dots = [] , angle_start = [], allimg = ['images/asami.jpg', 'images/chiaki.jpg', 'images/haruka.jpg', 'images/junon.jpg', 'images/mirin.jpg'] , alltxt = ['ASAMI', 'CHIAKI', 'HARUKA', 'JUNON', 'MIRIN'] , classname = ['viewport', 'round', 'thum'] var docFragment = document.createDocumentFragment(); /* * create document */ function makeClone() { $.each(classname, function () { if (this.length > 0) { //要素の生成 div = document.createElement('div'); div.className = this; if (this == 'viewport') { ul = document.createElement('ul'); ul.className = "overview"; drawCreates(ul); div.appendChild(ul); } ; if (this == 'thum') { var d = setDots(); div.appendChild(d); } ; docFragment.appendChild(div.cloneNode(true)); } }); self.append(docFragment); $slides = $('.overview').children(); $('.overview').append($slides.first().clone()); position = { 'width': width + dot_dia / 2, //round直径 'height': height + dot_dia / 2, 'top': -(dot_dia / 4), //round top position 'left': -(dot_dia / 4), //round left position }; $('.round').css(position); position = { 'width': width, 'height': height }; $('.overview li').css(position); return self; } function drawCreates(selector) { //sliderカルーセル画像配置 var fragment = document.createDocumentFragment(); // それぞれの要素の生成 var list = document.createElement('li'); var image = document.createElement('img'); $.each(allimg, function () { if ($(this).length > 0) { image.setAttribute('src', this); list.appendChild(image); fragment.appendChild(list.cloneNode(true)); } }); selector.appendChild(fragment); } //circleslider それぞれの設定 function setDots() { var fragment = document.createDocumentFragment(); $.each(allimg, function (index) { var div = document.createElement('div'); var txt = alltxt[index]; div.className = 'dot_' + index; //円形スライダーそれぞれにテキストを設定 $(div).html('<h3>' + txt + '</h3>'); //円形スライダーのひとつひとつの座標(x,y)を取得しtop,leftにpositionを設定 var angle = -((slidesAngle * index) + 180) % 360; var top = (Math.cos(Math.PI / 180 * angle) * (radius * 1.25) + centerX); var left = (Math.sin(Math.PI / 180 * angle) * (radius * 1.25) + centerY); position = { '-webkit-transform': 'matrix(1, 0, 0, 1, ' + left + ',' + top + ')', 'width': dot_dia, 'height': dot_dia }; $(div).css(position); //フラグメントにcirclesliderのドキュメントクローンを設定 fragment.appendChild(div.cloneNode(true)); //dotsとangle_start配列にそれぞれのデータをプッシュ dots.push({ "index": index, "angle": angle, "top": top, "left": left, "dot": div.cloneNode(true) }); angle_start.push({ "index": index, "angle": angle, "top": top, "left": left, "dot": div.cloneNode(true) }); }); return fragment; } function firstFunction() { makeClone(); } function secondFunction() { firstFunction(); } function _initialize() { secondFunction(); } _initialize(); return this; }; })(jQuery); |
上記javascriptで形成されたhtml文書が以下になります。#areaの中に円形のスライダ-、通常のスライダーカルーセルに必要なhtmlやstyleが追加されました。
html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Ak Circle Slider: A lightweight cross browser circular carousel. | 茅ヶ崎、藤沢、平塚のホームページ制作会社 OVER</title> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script src="ak_circle_slider.js" type="text/javascript"></script> <link href="ak_circle_slider.css" rel="stylesheet" type="text/css"/> <link href="https://fonts.googleapis.com/css2?family=Anton&family=Romanesco&display=swap" rel="stylesheet"> </head> <body> <h1>CYBER JAPAN</h1> <div id="area" style="width: 250px; height: 250px;"> <!-- makeclone()で形成 --> <div class="viewport"> <!-- drawCreates(selector)で形成 --> <ul class="overview" style="transform: translateX(-1130px);"> <li style="width: 250px; height: 250px;"><img src="images/asami.jpg"></li> <li style="width: 250px; height: 250px;"><img src="images/chiaki.jpg"></li> <li style="width: 250px; height: 250px;"><img src="images/haruka.jpg"></li> <li style="width: 250px; height: 250px;"><img src="images/junon.jpg"></li> <li style="width: 250px; height: 250px;"><img src="images/mirin.jpg"></li> <li style="width: 250px; height: 250px;"><img src="images/asami.jpg"></li> </ul> </div> <!-- makeclone()で形成 --> <div class="round" style="width: 312.5px; height: 312.5px; top: -31.25px; left: -31.25px;"> </div> <!-- makeclone()で形成 --> <div class="thum"> <!-- setDots()で形成 --> <div class="dot_0 current" style="transform: matrix(1, 0, 0, 1, -31.5336, -62.2868); width: 125px; height: 125px;"> <h3>ASAMI</h3> </div> <div class="dot_1" style="transform: matrix(1, 0, 0, 1, -85.2373, 113.37); width: 125px; height: 125px;"> <h3>CHIAKI</h3> </div> <div class="dot_2" style="transform: matrix(1, 0, 0, 1, 65.2269, 218.726); width: 125px; height: 125px;"> <h3>HARUKA</h3> </div> <div class="dot_3" style="transform: matrix(1, 0, 0, 1, 211.923, 108.183); width: 125px; height: 125px;"> <h3>JUNON</h3> </div> <div class="dot_4" style="transform: matrix(1, 0, 0, 1, 152.121, -65.4925); width: 125px; height: 125px;"> <h3>MIRIN</h3> </div> </div> </div> <div id="start_btn"></div> <div id="stop_btn"></div> </body> </html> |
createDocumentFragment()を使用して追加したhtmlドキュメントについて各々で簡単に解説していきたいと思います。
ドキュメント形成にはjavascript内のmakeClone()、drawCreates(selector)、setDots()の3つのメソッドを使用しています。
makecloneメソッドでcirclesliderで必要なパーツの一番外側のドキュメントをdocument.createElement()を使って3つ作成し、上から順に「’viewport’, ‘round’, ‘thum’」のクラス名を追加します。
drawCreateメソッドでスライダ-カル-セルで使用するimg要素とimg要素をラップするli要素を、使用する画像の数だけdocument.createElement()を使って作成し、img要素に対してsetAttribute(‘src’)でsrc属性を追加して、画像へのパスを設定します。
setDotsメソッドでスライダ-カルーセルで使用する画像分だけサークルスライダーの要素を作成します(ここでは5個)。
サ-クルスライダ-の要素各々の円に対する角度の比をangleに設定します。ここで少し細工が必要になります。-(マイナス)を追加することでサ-クルスライダ-の左右を反転します。+180することで上下を反転します。一度外して確認して頂ければと思います。
各々の(x,y)の値を取得してtopとleftに設定します。
dotsとangle_startの各配列に必要なデータを取得しておきます。これはドキュメント形成には無関係でスライダ-で使用しますが、setDotsメソッドに組み込んであるので一応入れておきます。このデ-タを確保しておくと確認作業や何か別途で機能を追加したい時などに役に立ちます。
javascriptのcreateDocumentFragment()を使用してのドキュメント実装解説は以上になります。次に本題のcirclesliderの実装の解説に入りたいと思います。
circleslider(円形スライダー)を実装する
createDocumentFragment()でのドキュメント形成の解説と同じようにcircleslider(円形スライダー)のメインの実装に必要なコードを一部抜き出して示します。
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
/* * Plugin_name:Ak_CircleSlider Author:Aturow Komori Author URI: https://over-creation-studio.jp/ Version: 1.0.1 License: Freeeeeeee < Please give me a job license > * */ $(document).ready(function () { // $("#area").ak_cslider_plug({intervalTime: 3000}); }); (function ($) { /* * start plugin */ $.fn.ak_cslider_plug = function (options) { /* * settings */ var settings = $.extend({ intervalTime: 5000, start: 0, slide_start: 0, }, options); var self = this; var width = self.innerWidth() //selector width , height = self.innerHeight() //selector height , radius = width / 2 //selector 半径 , dot_dia = 2.5 * width / 5 //円形スライダー一つの円の直径 , centerX = radius - dot_dia / 2 //slector(#area)の中心座標 , centerY = radius - dot_dia / 2 , dots = [] , angle_start = [] , allimg = ['images/asami.jpg', 'images/chiaki.jpg', 'images/haruka.jpg', 'images/junon.jpg', 'images/mirin.jpg'] , alltxt = ['ASAMI', 'CHIAKI', 'HARUKA', 'JUNON', 'MIRIN'] , slidesAngle = 360 / allimg.length //circlesleder 1回の移動半径 , slideCurrent = 0 , $slides; /* * circle slider */ //circle_slide() 1回の呼び出しでcirclesliderを1°移動する function circle_slide() { $.each($('.thum div'), function (index) { var i; // 360°で割った余りがiに返る i = -(angle_start[index].angle - settings.start) % 360; var top_a = (Math.cos(Math.PI / 180 * i) * (radius * 1.25) + centerX); var left_a = (Math.sin(Math.PI / 180 * i) * (radius * 1.25) + centerY); $(this).css('transform', 'matrix(1, 0, 0, 1, ' + left_a + ',' + top_a + ')'); //cicleslider5個のデフォルト角度に1°足す angle_start[index].angle++; }); } function move(index) { var slidesTotal = allimg.length; var slideIndex = (index) % slidesTotal; slideCurrent = slideIndex; var slide = $('.thum div'); slide.eq(slideCurrent).addClass('current'); slide.eq((slideCurrent - 1)).removeClass('current'); } function set_timer() { var j = 0; var loopFunc = function () { circle_slide(); //slide(); j += 1; if (slidesAngle >= j) { setTimeout(function () { loopFunc(); }, 10); } }; loopFunc(); settings.start++; move(settings.start); } function start_timer() { set_timer(); this.timer = setTimeout(function () { start_timer(); }, settings.intervalTime); } function firstFunction() { } function secondFunction() { firstFunction(); start_timer(); } function _initialize() { secondFunction(); } _initialize(); return this; }; })(jQuery); |
circleslide(円形スライダー)を動かすのに必要なメソッドはcircle_slide()、move()、set_timer()、start_timer()の4つ
まずcircle_slide() 1回の呼び出しで5つの円形スライダーが1°づつ移動します。1°移動した時の(x,y)の座標を取得してcssのtransformプロパティーを使用して回転させます。
ここでの一つのポイントがモジュロ演算です。モジュロ演算は被除数を除数で割った余りを返します。(angle_start[index].angle – settings.start)の値が1081だとしてもiに入る数値は360で割った余りの1です。iには除数以上の数値は入りません。ここではiには360以上の数値は入らないことになります。
二つ目のポイントはangle_start[index].angleです。setDotsメソッドで取得した連想配列のangleには円形スライダー5個のそれぞれのデフォルトの角度の値が入っています。circle_slide() 1回の呼び出しでそれぞれに1°づつ足されます。結果それぞれの(x,y)座標の値が変化します。
set_timer()関数内ではcircle_slide()関数を10ミリ秒ごとにslidesAngle回呼び出しています。slidesAngleはここでは360/5なので72になります。set_timer()1回の呼び出しでcirclesliderはそれぞれの隣の座標まで移動することになります。
start_timer() 関数でset_timer() 関数をsettings.intervalTimeミリ秒ごとに呼び出し続けることでtimerをクリアしない限りcirclesliderは回り続けます。
最後にmove(index) 関数でstylingに必要なcurrentクラスをつけはずしします。ここでもモジュロ演算を使用してslidesTotal以上(ここでは5)の値が入らないようにします。
以上でcirclesliderの実装の解説は終了になります。次に普通のカルーセルスライダーを実装して円形のスライダーと動きを同期させてみます。