2012年9月21日金曜日

svgとセレクタを駆使してカレンダーを作る

いろいろと試してみる内にsvgとセレクタとは実は非常に相性が良いとの結論に至りました.
というのも,htmlでは文書の構造上要素を記述すべき順序が基本的に左上から右下に向かって一並びとなるのに対し,svgではこの順序を自由に記述できるからです.

htmlにおいてもposition:absoluteを使えば絶対位置によるレイアウトを行うことができますが,table要素など階層構造がきっちりと定義されているものについては,中々難しい面があります.その点svgでは階層構造すら勝手に定義することができるため,工夫次第で実に様々な表現を行うことができます.

今回紹介するのはスクリプトなしにsvgとセレクタだけで作ったカレンダーです.前回のトランプカードを発展させたもので,中々面白い結果となっています.

実際の動作はこちらから.


特徴としては,次のような感じです.
  • 1年分の日付を月毎に表示することができる.
  • 月の指定にはハッシュ値(#d1〜#d12)を指定することとした.
  • 休日等の指定はスタイルシートで決め打ち.
あくまで静的な動作なため,スクリプトを使ったような「今日」をハイライトさせたり,遠い未来のカレンダーを作ると言ったことはしていません.(別途スクリプトを仕込めば可能ではあります.)

実装の方針は次のとおりです.
  • 卓上万年カレンダーのように日付を並べておいてそれを「ずらす」ことで月毎のカレンダーを得る.
  • svgのスタイルシートでは図形を「ずらす」事ができないため,use要素を使って基本となるカレンダーを7つコピーし,それぞれずらして重ねておく.
  • 重ねたカレンダーは非表示としておき,ハッシュで指定した月毎に1枚表示させるようにする.
    (:target擬似クラスと隣接セレクタを使って実現する)
  • 月毎に曜日と休日のスタイル設定をする.
  • 月送りのためのボタンについても同様.
構造としては前回とそれほど違いません.コード量こそ長めですが, パーツ毎に記述が面倒なだけで難しい内容ではありません.

※chromeでは月送りボタンが正しく動作しません.いろいろ試してみた結果,「a要素に自分自身のsvg文書片要素へのリンク(つまり「#xxx」)を設定した場合,リンクをクリックしても正しく動作しない」 ことが確認出来ました.ファイル名を指定すれば動作するのですが,それでは無用なリクエストが発生してしまいます.問題の報告をしてみたけれどどうなることやら…

<?xml version="1.0" standalone="no"?>
<svg width="280px" height="280px" viewBox="0 0 140 140" xmlns="http://www.w3.org/2000/svg" 
 xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
 preserveAspectRatio="none">
 <!--年毎に可変なスタイル-->
 <style>
  /*月の選択*/
  #m1:target~#ms2,
  #m2:target~#ms5,
  #m3:target~#ms5,
  #m4:target~#ms1,
  #m5:target~#ms3,
  #m6:target~#ms6,
  #m7:target~#ms1,
  #m8:target~#ms4,
  #m9:target~#ms0,
  #m10:target~#ms2,
  #m11:target~#ms5,
  #m12:target~#ms0{
   display:inline;
  }
  
  /*不必要な月を非表示に*/
  /*note:*でしていているのはdefs要素とuse要素とを同一視するため.*/
  #m2:target~* #d29,
  #m2:target~* #d30,
  #m2:target~* #d31,
  #m4:target~* #d31,
  #m6:target~* #d31,
  #m9:target~* #d31,
  #m11:target~* #d31{
   display:none;
  }
  
  /*日曜日の色*/
  #m1:target~* #dow5 use,
  #m2:target~* #dow2 use,
  #m3:target~* #dow2 use,
  #m4:target~* #dow6 use,
  #m5:target~* #dow4 use,
  #m6:target~* #dow1 use,
  #m7:target~* #dow6 use,
  #m8:target~* #dow3 use,
  #m9:target~* #dow0 use,
  #m10:target~* #dow5 use,
  #m11:target~* #dow2 use,
  #m12:target~* #dow0 use{
   fill:red;
  }

  /*土曜日の色*/
  #m1:target~* #dow4 use,
  #m2:target~* #dow1 use,
  #m3:target~* #dow1 use,
  #m4:target~* #dow5 use,
  #m5:target~* #dow3 use,
  #m6:target~* #dow0 use,
  #m7:target~* #dow5 use,
  #m8:target~* #dow2 use,
  #m9:target~* #dow6 use,
  #m10:target~* #dow4 use,
  #m11:target~* #dow1 use,
  #m12:target~* #dow6 use{
   fill:blue;
  }

  /*祝日の色*/
  #m1:target~* #d1 use,
  #m1:target~* #d14 use,
  #m2:target~* #d11 use,
  #m3:target~* #d20 use,
  #m4:target~* #d29 use,
  #m5:target~* #d3 use,
  #m5:target~* #d4 use,
  #m5:target~* #d5 use,
  #m5:target~* #d6 use,
  #m7:target~* #d15 use,
  #m9:target~* #d16 use,
  #m9:target~* #d23 use,
  #m10:target~* #d14 use,
  #m11:target~* #d3 use,
  #m11:target~* #d4 use,
  #m11:target~* #d23 use,
  #m12:target~* #d23 use{
   fill:red;
  }
 </style>
 <!--変化しないスタイル-->
 <style>
  #day circle:first-child{
   opacity:0.5;
  }
  #month * use{
   fill:green;
  }

  /*テキストの設定*/
  #month text{
   text-anchor:middle;
   dominant-baseline:central;
   font-size:10px;
   font-family:sans-serif;
   fill:white;
   font-weight:bold;
   filter:url(#textShadow);
  }
  
  /*ヘッダーテキスト*/
  #headtext{
   text-anchor:middle;
   font-size:10px;
   font-weight:bold;
  }
  #headtext tspan{
   display:none;
  }
  #m1:target~#headtext tspan:nth-child(1),
  #m2:target~#headtext tspan:nth-child(2),
  #m3:target~#headtext tspan:nth-child(3),
  #m4:target~#headtext tspan:nth-child(4),
  #m5:target~#headtext tspan:nth-child(5),
  #m6:target~#headtext tspan:nth-child(6),
  #m7:target~#headtext tspan:nth-child(7),
  #m8:target~#headtext tspan:nth-child(8),
  #m9:target~#headtext tspan:nth-child(9),
  #m10:target~#headtext tspan:nth-child(10),
  #m11:target~#headtext tspan:nth-child(11),
  #m12:target~#headtext tspan:nth-child(12){
   display:inline;
  }

  /*ボタン*/
  #m1:target~#b1,
  #m2:target~#b2,
  #m3:target~#b3,
  #m4:target~#b4,
  #m5:target~#b5,
  #m6:target~#b6,
  #m7:target~#b7,
  #m8:target~#b8,
  #m9:target~#b9,
  #m10:target~#b10,
  #m11:target~#b11,
  #m12:target~#b12{
   display:inline;
  }

  /*曜日*/
  #dow{
   text-anchor:middle;
   dominant-baseline:text-after-edge;
   font-size:7px;
   font-family:sans-serif;
   font-weight:bold;
   fill:green;
   filter:url(#textShadow2);
  }
  #dow text:first-child{
   fill:red;
  }
  #dow text:last-child{
   fill:blue;
  }
 </style>
 <g clip-path="url(#cp)">
  <!--targetとするためのダミー-->
  <rect id="m1"/>
  <rect id="m2"/>
  <rect id="m3"/>
  <rect id="m4"/>
  <rect id="m5"/>
  <rect id="m6"/>
  <rect id="m7"/>
  <rect id="m8"/>
  <rect id="m9"/>
  <rect id="m10"/>
  <rect id="m11"/>
  <rect id="m12"/>
  
  <!--カレンダー構成に関わる定義-->
  <defs>
   <!--日にあたる図形を定義する-->
   <g id="day">
    <circle cx="10" cy="10" r="9"/>
    <circle cx="10" cy="10" r="7"/>
   </g>
   <g id="month">
    <!--カレンダーの縦列-->
    <g id="dow0">
     <g id="d1" transform="translate(0,0)">
      <use xlink:href="#day"/><text x="10" y="10">1</text>
     </g>
     <g id="d8" transform="translate(0,20)">
      <use xlink:href="#day"/><text x="10" y="10">8</text>
     </g>
     <g id="d15" transform="translate(0,40)">
      <use xlink:href="#day"/><text x="10" y="10">15</text>
     </g>
     <g id="d22" transform="translate(0,60)">
      <use xlink:href="#day"/><text x="10" y="10">22</text>
     </g>
     <g id="d29" transform="translate(0,80)">
      <use xlink:href="#day"/><text x="10" y="10">29</text>
     </g>
    </g>
    <g id="dow1" transform="translate(20,0)">
     <g id="d2" transform="translate(0,0)">
      <use xlink:href="#day"/><text x="10" y="10">2</text>
     </g>
     <g id="d9" transform="translate(0,20)">
      <use xlink:href="#day"/><text x="10" y="10">9</text>
     </g>
     <g id="d16" transform="translate(0,40)">
      <use xlink:href="#day"/><text x="10" y="10">16</text>
     </g>
     <g id="d23" transform="translate(0,60)">
      <use xlink:href="#day"/><text x="10" y="10">23</text>
     </g>
     <g id="d30" transform="translate(0,80)">
      <use xlink:href="#day"/><text x="10" y="10">30</text>
     </g>
    </g>
    <g id="dow2" transform="translate(40,0)">
     <g id="d3" transform="translate(0,0)">
      <use xlink:href="#day"/><text x="10" y="10">3</text>
     </g>
     <g id="d10" transform="translate(0,20)">
      <use xlink:href="#day"/><text x="10" y="10">10</text>
     </g>
     <g id="d17" transform="translate(0,40)">
      <use xlink:href="#day"/><text x="10" y="10">17</text>
     </g>
     <g id="d24" transform="translate(0,60)">
      <use xlink:href="#day"/><text x="10" y="10">24</text>
     </g>
     <g id="d31" transform="translate(0,80)">
      <use xlink:href="#day"/><text x="10" y="10">31</text>
     </g>
    </g>
    <g id="dow3" transform="translate(60,0)">
     <g id="d4" transform="translate(0,0)">
      <use xlink:href="#day"/><text x="10" y="10">4</text>
     </g>
     <g id="d11" transform="translate(0,20)">
      <use xlink:href="#day"/><text x="10" y="10">11</text>
     </g>
     <g id="d18" transform="translate(0,40)">
      <use xlink:href="#day"/><text x="10" y="10">18</text>
     </g>
     <g id="d25" transform="translate(0,60)">
      <use xlink:href="#day"/><text x="10" y="10">25</text>
     </g>
    </g>
    <g id="dow4" transform="translate(80,0)">
     <g id="d5" transform="translate(0,0)">
      <use xlink:href="#day"/><text x="10" y="10">5</text>
     </g>
     <g id="d12" transform="translate(0,20)">
      <use xlink:href="#day"/><text x="10" y="10">12</text>
     </g>
     <g id="d19" transform="translate(0,40)">
      <use xlink:href="#day"/><text x="10" y="10">19</text>
     </g>
     <g id="d26" transform="translate(0,60)">
      <use xlink:href="#day"/><text x="10" y="10">26</text>
     </g>
    </g>
    <g id="dow5" transform="translate(100,0)">
     <g id="d6" transform="translate(0,0)">
      <use xlink:href="#day"/><text x="10" y="10">6</text>
     </g>
     <g id="d13" transform="translate(0,20)">
      <use xlink:href="#day"/><text x="10" y="10">13</text>
     </g>
     <g id="d20" transform="translate(0,40)">
      <use xlink:href="#day"/><text x="10" y="10">20</text>
     </g>
     <g id="d27" transform="translate(0,60)">
      <use xlink:href="#day"/><text x="10" y="10">27</text>
     </g>
    </g>
    <g id="dow6" transform="translate(120,0)">
     <g id="d7" transform="translate(0,0)">
      <use xlink:href="#day"/><text x="10" y="10">7</text>
     </g>
     <g id="d14" transform="translate(0,20)">
      <use xlink:href="#day"/><text x="10" y="10">14</text>
     </g>
     <g id="d21" transform="translate(0,40)">
      <use xlink:href="#day"/><text x="10" y="10">21</text>
     </g>
     <g id="d28" transform="translate(0,60)">
      <use xlink:href="#day"/><text x="10" y="10">28</text>
     </g>
    </g>
   </g>
   <!--ひと月分のカレンダーをずらして左に配置する.→月毎に開始曜日を変化させるため.-->
   <g id="calendarFace">
    <use xlink:href="#month" transform="translate(0,20)"/>
    <use xlink:href="#month" transform="translate(-140,40)"/>
   </g>
   <!--月送りボタン-->
   <path id="button" d="m0,5 l10,-5 v10 z"/>
  </defs>
  
  <!--その他の効果の定義-->
  <defs>
   <!--余計な部分を隠す-->
   <clipPath id="cp">
    <rect width="100%" height="100%"/>
   </clipPath>
   <!--日付のフィルター-->
   <filter id="textShadow" filterUnits="userSpaceOnUse" x="0" y="0" width="20" height="20" filterRes="160,160">
    <feOffset in="SourceAlpha" dx="1" dy="1"/>
    <feMerge>
     <feMergeNode/>
     <feMergeNode in="SourceGraphic"/>
    </feMerge>
   </filter>
   <!--曜日のフィルター-->
   <filter id="textShadow2" filterUnits="userSpaceOnUse" x="0" y="-10" width="140" height="20" filterRes="1120,160">
    <feOffset in="SourceAlpha" dx="0.5" dy="0.5"/>
    <feMerge>
     <feMergeNode/>
     <feMergeNode in="SourceGraphic"/>
    </feMerge>
   </filter>
  </defs>
  
  <!--年月部-->
  <text id="headtext" y="10" x="70">
   2013 年 
   <tspan>1</tspan>
   <tspan>2</tspan>
   <tspan>3</tspan>
   <tspan>4</tspan>
   <tspan>5</tspan>
   <tspan>6</tspan>
   <tspan>7</tspan>
   <tspan>8</tspan>
   <tspan>9</tspan>
   <tspan>10</tspan>
   <tspan>11</tspan>
   <tspan>12</tspan>
   月
  </text>
  
  <!--月送りボタン-->
  <g id="b1" display="none">
   <a xlink:href="#m2"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b2" display="none">
   <a xlink:href="#m1"><use xlink:href="#button"/></a>
   <a xlink:href="#m3"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b3" display="none">
   <a xlink:href="#m2"><use xlink:href="#button"/></a>
   <a xlink:href="#m4"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b4" display="none">
   <a xlink:href="#m3"><use xlink:href="#button"/></a>
   <a xlink:href="#m5"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b5" display="none">
   <a xlink:href="#m4"><use xlink:href="#button"/></a>
   <a xlink:href="#m6"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b6" display="none">
   <a xlink:href="#m5"><use xlink:href="#button"/></a>
   <a xlink:href="#m7"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b7" display="none">
   <a xlink:href="#m6"><use xlink:href="#button"/></a>
   <a xlink:href="#m8"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b8" display="none">
   <a xlink:href="#m7"><use xlink:href="#button"/></a>
   <a xlink:href="#m9"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b9" display="none">
   <a xlink:href="#m8"><use xlink:href="#button"/></a>
   <a xlink:href="#m10"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b10" display="none">
   <a xlink:href="#m9"><use xlink:href="#button"/></a>
   <a xlink:href="#m11"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b11" display="none">
   <a xlink:href="#m10"><use xlink:href="#button"/></a>
   <a xlink:href="#m12"><use xlink:href="#button" transform="translate(140,0),scale(-1,1)"/></a>
  </g>
  <g id="b12" display="none">
   <a xlink:href="#m11"><use xlink:href="#button"/></a>
  </g>
  
  <!--曜日部-->
  <g id="dow" transform="translate(0,19)">
   <text x="10">日</text>
   <text x="30">月</text>
   <text x="50">火</text>
   <text x="70">水</text>
   <text x="90">木</text>
   <text x="110">金</text>
   <text x="130">土</text>
  </g>
  
  <!--カレンダー部-->
  <!--卓上万年カレンダーのように全体の位置をずらすことで開始曜日を切り替える-->
  <use id="ms0" xlink:href="#calendarFace" transform="translate(0,0)" display="none"/>
  <use id="ms1" xlink:href="#calendarFace" transform="translate(20,0)" display="none"/>
  <use id="ms2" xlink:href="#calendarFace" transform="translate(40,0)" display="none"/>
  <use id="ms3" xlink:href="#calendarFace" transform="translate(60,0)" display="none"/>
  <use id="ms4" xlink:href="#calendarFace" transform="translate(80,0)" display="none"/>
  <use id="ms5" xlink:href="#calendarFace" transform="translate(100,0)" display="none"/>
  <use id="ms6" xlink:href="#calendarFace" transform="translate(120,0)" display="none"/>
 </g>
</svg>

0 件のコメント:

コメントを投稿