Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                section#app(v-cloak)
  div.clock
    div.clock__cover
      div.clock__cover__highlight
    div.clock__shadow
    div.clock__dials
      h2.clock__dials__logo
        |SE1KO
      - var n = 1
      while n < 13
        div.clock__dials__number(class='--' + n) #{n}
        - n ++
      div.digital
        div.digital__time 
          |{{digitalHours}}
          span.digital__delimiter :
          |{{digitalMinutes}}
          span.digital__delimiter :
          |{{digitalSeconds}}
          span.digital__milliseconds
            |.{{digitalMilliseconds}}
    div.clock__hands
      div.hour-hand(
        :style="analogStyles.hours"
        @mousedown="grabHand($event, 'hour')",
      )
      div.minute-hand(
        :style="analogStyles.minutes",
        @mousedown="grabHand($event, 'minute')",
      )
      div.second-hand(
        :style="analogStyles.seconds",
        @mousedown="grabHand($event, 'second')",
      )
      div.pin
              
            
!

CSS

              
                @import url('https://fonts.googleapis.com/css?family=Press+Start+2P')
@import url('https://fonts.googleapis.com/css?family=Open+Sans')
@import url('https://fonts.googleapis.com/css?family=Ultra')

.absolute-fit
  position absolute
  top 0
  left 0
  right 0
  bottom 0
  
.absolute-center
  position absolute
  top 50%
  left 50%
  
circle($_radius)
  width $_radius
  height @width
  border-radius 100%


*
  box-sizing border-box
[v-cloak]
  visibility hidden

#app
  @extends .absolute-fit
  background-image url('http://www.toquebem.com/wp-content/uploads/2015/01/Textura-Madeira-2.jpg')
  background-size cover

.clock
  @extends .absolute-center
  user-select none
  circle(300px)
  transform translate(-50%, -50%)
  &__cover
    @extends .absolute-fit
    &__highlight
      circle(20px)
      position absolute
      top 12%
      left 22%
      z-index 10
      background #FFF
      opacity 0.8
      box-shadow 5px 5px 3px 4px rgba(#000, 0.1)
  &__shadow
    position absolute
    top 0
    left 0
    circle(100%)
    box-shadow -4px -4px 5px 13px rgba(#000,0.2) inset
  &__dials
    circle(100%)
    background-image url('http://coliss.com/wp-content/uploads-2012/2012011606-04.png')
    border solid 6px #2f2620
    box-shadow 4px 4px 5px 4px rgba(#000, 0.5)
    &__logo
      font-family 'Ultra'
      font-size 14px
      text-align center
      margin-top 60px
    &__number
      @extends .absolute-fit
      font-family 'Open Sans'
      font-size 18px
      text-align center
      padding-top 0.5em
      font-size 24px
      transform-origin center
      for num in 1..12
        &.--{num}
          $deg = num*30 deg
          transform rotate($deg)
      
.digital
  width 180px
  margin 0 auto
  margin-top 90px
  padding 0.7em 0.4em 0.5em 0.4em
  background #b8c3b5
  box-shadow 1px 1px 1px 2px rgba(#000, 0.3) inset
  text-align center
  &__time
    color #111
    font-size 16px
    font-family 'Press Start 2P', cursive
    display inline-block
    text-shadow 1px 1px 1px rgba(#000, 0.4)
  &__milliseconds
    font-size 8px
    

.clock__hands
  position absolute
  top 50%
  left 50%

.__hand
  background #000
  position absolute
  box-shadow 0px 0px 2px 2px rgba(#222, 0.3)
  border-radius 2px

createHand($_width, $_height, $_top, $_zIndex)
  @extends .__hand
  width  ($_width)px
  height ($_height)px
  top (-1 * $_top) px
  left (-1 *$_width/2) px
  transform-origin center ($_top)px
  z-index $_zIndex
  &.hand--modify
    transition-property transform
    transition-duration 0.5s
  
.hour-hand
  createHand(10, 100, 10, 1)

.minute-hand
  createHand(6, 130, 12, 2)

.second-hand
  createHand(2, 135, 14, 3)

.pin
  $w = 5px
  position absolute
  top (-1 * $w/2)
  left (-1 * $w/2)
  width $w
  height @width
  background #000
  border solid 1px #EEE
  border-radius 100%
  z-index 4
              
            
!

JS

              
                /*
vue.js ファイルでは window.Vue にクラス関数が定義される。この Vueクラスに対してnew演算子を用いて得られるオブジェクトを俗にインスタンスと呼び、MVVMにおけるView-Modelの役割を果たす。

インスタンス生成時に引数へオプションを渡しておくことで、どのModel / View と紐づくかが指定できるほか、内部にもつ DOM Listener と Data Binding に関与する挙動、コンポーネントの設定を指定することが出来る。
 オプション内に宣言されるコールバック関数において、(特定の条件で使用された場合を除いて)thisはこのVueインスタンスを参照し、これはAPIにおける vm に相当する。これにより this を介して自身のdataやmethodsを簡単に参照できるようになっている。


  ※実際のECMA Script5では言語仕様にクラス/インスタンスが存在せず、他の言語の考え方を拝借するような形で呼ばれているだけ。ただしECMA Script6では正式にクラス構文が採用されおり、ES6で開発されているVueにおいては誤りではない。
*/
var App = new Vue({
  
  // =====================
  // View の指定
  // =====================
  /*
  複数の指定方法がある(優先順位は未検証)
  1. HTML上にテンプレートを記述し、elオプションへCSSセレクタを指定
  2. templateオプションへ直接 HTML文字列を記述
  3. <script type="text/x-template" id="someId"> をHTMLに記述し、templateオプションでid指定する
      
  4. Vue独自のrender function(描画関数)形式で記述
      ※通常この手法を使うことは無い。描画関数がもたらすプログラマブルな挙動は pug(jade)で代替できる。
      
  5. node環境下でvue-loaderを使用し、<template> <script> タグの形式でインスタンスを生成する(Drawser)
      ※この場合 el/template オプションが自動で挿入される、new Vue()の記述が不要など一般的な Vue の記法と異なる部分が多いので混乱しやすい。
  */
  el: '#app',
  // template: '<section id="app"><h1>時計</h1>....</section>',
  
  
  // =====================
  // Model
  // =====================
  /*
  MVVMのうち、内部データに関与する領域。data, computed, storeオプションなどが該当する。
  Vueの持つデータバインディング機構によってこれらは"リアクティブ"な状態を持っており、値が変更されたらViewの更新や以下のwatchのような監視用コールバックが実行される。
  */
  
  // == [dataオプション] ==
  /*
    https://jp.vuejs.org/v2/api/#data
    最も基本的なデータを設定する。ここで宣言したプロパティはテンプレート内のVue構文で直接参照することができるようになる。
    複雑なものを含めても問題ないが、特性や用途に応じて適宜以下の算出プロパティに委ねた方がよい。
    今回の場合はDate.getHours()の仕様や秒のインクリメント計算の関係から時・分・秒を整数値型で管理したいので、dataでは最も純粋な整数値のみを保持する。
    
    dataオプションはインスタンス生成時に vm.$dataへ複製、使用されるが、同時にVueインスタンスの直下プロパティにコピーされる(プロキシ)。テンプレート上で直接参照できる(vm.hours/ this.hours などではなく hours だけで呼び出せる)値はVueインスタンスの直下プロパティに限り、プロパティ名の先頭にアンダースコア _ かドル $ を使用した場合はプロキシが行われないため直接参照が出来なくなる($data._hoursとして経由する必要がある)。
  */
  data: function(){
    return{
      hours:        24,
      minutes:      0,
      seconds:      0,
      milliseconds: 0,
    } 
  },
 
  // == [watch オプション] ==
  /*
   データ変更時に追加で処理を行いたい時に設定する。
   dataのものと同じプロパティ名を使用すると監視対象として指定が可能。コールバック関数には第一引数に更新後の値(newValue)が、第二引数に更新前の値(oldValue)が渡される。
   今回の場合はデータが24/60/60/100を最大値として0に戻る値なので余剰計算によるチェックを行っている。
  */
  watch: {
    hours: function(newValue){
      return newValue % 24
    },
    minutes: function(newValue){
      return newValue % 60
    },
    seconds: function(newValue){
      // 監視中に60秒に到達したら分表示を更新したいので、精度の向上も兼ねて表示更新を行う
      this._refreshDisplay()
      return newValue % 60
    },
    milliseconds: function(newValue){
      return newValue % 100
    },
  },
  
  
  // == [computed オプション] ==
  /*
    訳語は算出プロパティ。Modelのうちの1つで、data と同じようにインスタンスの直下プロパティとして登録される。
    data で管理している値の副産物的な情報(合計、平均値、単位を追加した文字列など)を作る場合に利用すると良く、情報としての値と表示としての値を切り分けやすくなる。
    今回では整数値計算を容易にするため data を数値型とし、computed ではゼロ埋め文字列や値から算出される針の角度を指定している。
  */
  computed:{
    // --アナログ表示用--
    /*
     針の角度をCSSで指定する為に値を算出しておく
    */   
    analogStyles: function(){
      function generateStyle(deg){
        return {transform: 'rotate(' + (180 + deg) + 'deg)'}       
      }
      var styles = {
        hours:        generateStyle( this._getHoursDegree()   ),
        minutes:      generateStyle( this._getMinutesDegree() ),
        seconds:      generateStyle( this._getSecondsDegree() ),
      } 
      return styles
    },
    // --デジタル表示用--
    /*
      各値はゼロ埋めを施した2桁の文字列
      冗長的だが統一する方法が拡張プラグインしかないので以下のままで。
    */
    digitalHours: function(){
      return this._getZerofillString(this.hours)
    },
    digitalMinutes: function(){
      return this._getZerofillString(this.minutes)
    },
    digitalSeconds: function(){
      return this._getZerofillString(this.seconds)
    },
    digitalMilliseconds: function(){
      return this._getZerofillString(this.milliseconds)
    },
  },
 
  
  // * =========== *
  // * Life Cycles *
  // * =========== *
  /*
  https://jp.vuejs.org/v2/api/#オプション-ライフサイクルフック
  
  コンポーネントの生成フローの途中で処理を噛ませたい場合に使用するフック。
  細かい区分があるので詳しくはAPI参照。
  以下のmountedは「コンポーネント(インスタンス)がHTML内に挿入された直後」に実行され、用途のイメージとしてはwindow.onloadに近い。
  今回の場合は時刻表示の初期化と秒加算ループのスタートを行っている。
  */  
  mounted(){
    this._setDate()
    this._pendulum()
    window.addEventListener('focus', this._refreshDisplay)
  },
 
  
  // =======
  // Methods
  // =======
  /*
  DOM Listeners(v-on)やその他オプションのコールバック関数内から呼び出して使用する汎用のメソッド群。dataやcomputedと同じくVueインスタンス直下のプロパティなので、テンプレートからの直接参照やオプション内の各所から this 経由の参照で使用できる。
  
  プロキシされたdataやcomputedと名前空間が同一なので、同じ名前で宣言するとmethodsの方が上書きされる。また、そもそもmethodsにはプロキシの機能が存在しないので _render のような命名だと内部関数と衝突することがあることに注意すること。
  ※ Vueの作者が元Google所属のためか、ユーザーの命名規則もGoogleのガイドラインを想定している向きがある。Googleのガイドラインではプライベート関数には先頭ではなく末尾にアンダースコアを付けるのが正しいらしい。
  */
  methods:{
    /**
     * 秒更新のループを起動する
     */
    _pendulum: function(){
      var _this = this
      
      // 1秒ごとに秒表示を更新
      ;(function secondPendulum(){
        setTimeout(function(){
          _this.seconds++
          secondPendulum()
        }, 1000)
      })()
      
      // 1msごとにミリ秒表示を更新
      ;(function millisecondPendulum(){
        setTimeout(function(){
          _this.milliseconds++
          millisecondPendulum()
        }, 1)
      })()
    },
    
    
    /**
     * 現在の秒数が00になったら時刻全体の再取得を行う
     */
    _refreshDisplay: function(){
      if(this.seconds > 59) this._setDate()
    },
    
    
    /**
     * JavaScriptの Dateインスタンスから現在の時刻をdataへ格納する
     */
    _setDate: function(){
      var now           = new Date()
      this.hours        = now.getHours()
      this.minutes      = now.getMinutes()
      this.seconds      = now.getSeconds()  
      this.milliseconds = now.getMilliseconds()
    },
    

    /**
     * デジタル表示用に2桁ゼロ埋めを行う
     * @param {number} value 数値
     * @param {divisor} divisor 除数
     * @return {string} ゼロ埋めした文字列
     */
    _getZerofillString: function(value, divisor){
      var zeroFill = '00' + value
      return zeroFill.slice(-2)
    },

    
    /**
     * アナログ時計の長針の角度
     */
    _getHoursDegree: function(){
      var DEGREE_PER_HOUR = 30
      var deg = DEGREE_PER_HOUR * this.hours
      deg += DEGREE_PER_HOUR * (this.minutes/60) 
      return deg
    },
    
    
    /**
     * アナログ時計の分針の角度
     */
    _getMinutesDegree: function(number){
      var DEGREE_PER_MINUTE = 6
      var deg = DEGREE_PER_MINUTE * this.minutes
      deg += DEGREE_PER_MINUTE * (this.seconds/60)
      return deg
    },
    
    
    /**
     * アナログ時計の秒針の角度
     */
    _getSecondsDegree: function(value, divisor){
      var DEGREE_PER_SECOND = 6
      var deg = DEGREE_PER_SECOND * this.seconds
      return deg
    },
    
  },
})
              
            
!
999px

Console