※スマホ対応はしてません。

カテゴリー: JavaScript

新しいChromeでスクロールが取れない? scrollingElement?

カテゴリー: JavaScript

知り合いに聞かれて気づいたんですが、Google Chromeのv61からスクロールの取得方法が変わったっぽい。とりあえずこんなコードで取れます。

var top = document.documentElement.scrollTop || document.body.scrollTop

なんだこれ

Chrome 60までとか未対応のやつは document.documentElement.scrollTop が常に 0 を返すので、 || で繋げてやるとどっちに対応してる環境でも動きます。

事前にUAから分岐して要素を分けて、みたいなことしてると駄目なので、こっちがおすすめです。そんなに実行コストかからんでしょ。知らんけど。

Chromeは変わったけどSafariはまだ変わってないので、対応しときましょう。

あとEdgeも document.body の方みたい。IEは document.documentElement だったのに。はしごを外されちゃったね。

scrollingElement なるものが

で、ちらちら見てたら scrollingElement というものがあるらしい。Safariもこいつに対応してるけど、IEは対応してない。(Edgeはいける。) IEを無視できるならこれで一発ですね。

var top = document.documentElement.scrollTop;

らくちん~。

(まだMDNに日本語版がない。これは翻訳チャンスだ。)

対応まとめ

環境 scrollingElement documentElement body
Firefox ✅ ✅ 0
Chrome 61 ✅ ✅ 0
Chrome 60 ✅ 0 ✅
Safari ✅ 0 ✅
IE undefined ✅ 0
Edge ✅ 0 ✅

試してみたい方はこちらをどうぞ。

参考

“VCBuild.exe”がないと言われたけど、windows-build-toolsを入れたら解決したよ。

カテゴリー: JavaScript

正直あんまりわかってないけどまあ解決したのでメモしておく。

ざっくり

管理者権限のPowerShellで npm install --global windows-build-tools したら解決した。

  • https://www.npmjs.com/package/windows-build-tools

環境

  • node v4.5.0
  • Windows 10

なお現在node.jsの最新バージョンはv8.4です。

問題: “VCBuild.exe”がないと言われる

何かをインストールしようとしたとき、こんなエラーが出ました。

C:\Users\Ginpei\project\foo>npm install
> contextify@0.1.15 install C:\Users\Ginpei\project\foo\node_modules\jsdom\node_modules\contextify
> node-gyp rebuild


C:\Users\Ginpei\project\foo\node_modules\jsdom\node_modules\contextify>if not defined npm_config_node_gyp (node "C:\Users\Ginpei\AppData\Roaming\nvm\v4.5.0\node_modules\npm\bin\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild )  else (node "" rebuild )
Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
MSBUILD : error MSB3428: Could not load the Visual C++ component "VCBuild.exe". To fix this, 1)  install the .NET Framework 2.0 SDK, 2) install Microsoft Visual Studio 2005 or 3) add the loca tion of the component to the system path if it is installed elsewhere.  [C:\Users\Ginpei\project\foo\node_modules\jsdom\node_modules\contextify\build\binding.sln]
gyp ERR! build error
...

MSBUILD : error MSB3428: Could not load the Visual C++ component “VCBuild.exe”.

node-gypでこける。なんかFAQだった気がする。

対応

MS Visual Studioをインストールすればいいのかなーと思ったけど、windows-build-toolsなるnpmパッケージでいけるらしい。npmパッケージじゃないのもあるのかなあ。

Windows PowerShellを管理者権限で起動

管理者権限じゃないとエラー。

>npm install --global --production windows-build-tools

-
> windows-build-tools@1.3.2 postinstall C:\Program Files\nodejs\node_modules\windows-build-tools

> node ./lib/index.js

Downloading BuildTools_Full.exe
[>                                            ] 0.0% (0 B/s)
Downloading python-2.7.13.msi
[>                                            ] 0.0% (0 B/s)
Downloaded python-2.7.13.msi. Saved to C:\Users\Ginpei\.windows-build-tools\python-2.7.13.msi.
Starting installation...
Please restart this script from an administrative PowerShell!
The build tools cannot be installed without administrative rights.
To fix, right-click on PowerShell and run "as Administrator".
npm ERR! Windows_NT 10.0.15063
...

Please restart this script from an administrative PowerShell!

というわけで、言われた通りに管理者権限でPowerShellを起動。

右クリックしてRun as administrator

で、この一行です。

npm install --global --production windows-build-tools

ここでツールをビルドしてるっぽくて相当時間かかった。

10分くらいかかった。

終わったら npm install し直すと、今度は成功した。やったね。

おしまい。

アンインストール

どうもwindows-build-toolsインストール前の状態への戻し方がわからない。npmのパッケージを削除してみたが、特に問題なく npm install が成功するようになってしまった。(まあ別にいいんだけど!)

>npm uninstall --global windows-build-tools

WindowsのSystem settingsから “Add or remote program” で一覧を開いて、今日インストールされたものの中からそれっぽいものを探す。どうやら以下の三つ。

  • Python 2.x.x
  • Microsoft Visual C++ Build Tools
  • Microsoft Visual C++ 2015 Redistributable (x64)

アンインストールするコマンドっぽいものはドキュメントに書いてあるんだけど、使い方がわからない……。これ↓と同じ症状。

参考

Vue+Webpackの開発がすごい楽ちんだ。(Vue.js始めるおれおれアドベントカレンダー2016 – 17日目分)

カテゴリー: JavaScript

Vue.js始めるおれおれアドベントカレンダー2016 – 17日目分(28日公開)

これこれ。

インストール

vue-webpack-boilerplate自体は特にインストール作業はなさそうなんだけど、その前にvue-cliが必要です。

$ npm install -g vue-cli

これでコマンドラインから vue コマンドが使えるようになります。 “vue-cli” はそのコマンドをインストールする的なやつで、ブラウザが読み込む vue.js とは別物。

開始

出力結果は省略してるけど、こんな感じ。

$ vue init webpack my-project
$ cd my-project
$ yarn install

開発サーバー起動。

$ npm run dev

Listening at http://localhost:8080

URLを開くと最初のページが出てきます。やったね。

テンプレートから生成された画面。

Ctrl+C で終了。

大丈夫そうなので、今回生成されたファイルを見ていきます。

(さらに…)

propsのvalidationをさらっと書けるvue-props-templateを作りました。(Vue.js始めるおれおれアドベントカレンダー2016 – 18日目)

カテゴリー: JavaScript

Vue.js始めるおれおれアドベントカレンダー2016 – 18日目

作りましたというか、まだもうちょっといじろうと思ってるんだけど、動きはします。

ginpei/vue-props-template: Write vue’s props through template literal.

Reactもそうなんだけど、オブジェクトを入れ子にしてく書き方、なかなか面倒じゃないですか。そういうのをなんかこう装飾子みたいにさらってできねえかなーそういやテンプレート文字列は改行も含められるんだなーあれでやれたらいいなーと考えて作ってみました。

propsの検証

Vueの機能として、型等を指定することができます。

こんな感じ。

const MyComponent = {
  props: {
    simpleString: { type: String },
    requiredNumber: { type: Number, required: true },
    defaultBoolean: { type: Boolean, default: true },
    validatedObject: {
      validator: function (value) {
        return (value.flag1 && value.flag2)
      }
    }
  }
}

vue-props-template

さっきの指定を、だいたいこう書けるようになります。

const pt = require('vue-props-template')

const MyComponent = {
  props: pt`
    string simpleString
    required number requiredNumber
    boolean defaultBoolean = true
  `
}

どうでしょうか、こういうの。

あとは validatedObject をどうしようかなあというところです。現状こんなん。

const pt = require('vue-props-template')

const MyComponent = {
  props: pt.extend(pt`
    object validatedObject
    `, {
      validatedObject: {
        validator: function (value) {
          return (value.flag1 && value.flag2)
        }
      }
    }
  }
}

よくわからないこと

便利?

個人的にはあんな風に書けたら便利だと思ってるんだけど、他の人はどうなんだろ。まあ自分で使う用でも構わないんだけど。

既存でこういうのないか?

ちょっと探して見つからなかったけどありそうで怖い。

validator の扱い

埋め込み値 ${…} の中に関数を書くのってどうかなあ。別に良いかなあ。例えばこういうの。

props: pt`
  validatedObject as ${function (value) {
    return (value.flag1 && value.flag2)
  }}
`

あーこういうのも悪くないかなあ。どうかなあ。

この場合、他の「型 名前」の順の書き方と違ってきちゃうけど、んーまあ良いか。

最近のライブラリの作り方

Vue本体を参考に、ブラウザで読み込んでもNode.js(vueify)経由でも動くようにはした。でも今はそれを直接元コードに書いてて、なんかこう「ビルド」をしてそういうのを出力するやり方の方が良いだろうか。

あとバージョン管理も実はあんまりよくわかってない。semverは良いんだけど、なんか手動でぽちぽちやるんじゃなくてnpm scriptに更新コマンドも用意して、なんて話を前聞いた気がする。

他にやること

これ全部終わったらv1.0.0ということにしようかなと思ってます。

  • validator の扱い
  • バベる
  • ドキュメントの見直し
  • GitHub pagesの充実
  • npm登録

そうか、要素に触るなら$refsとmountedを組み合わせれば良いのか。(Vue.js始めるおれおれアドベントカレンダー2016 – 16日目分)

カテゴリー: JavaScript

Vue.js始めるおれおれアドベントカレンダー2016 – 16日目分(24日公開)

しゅんしゅん動くよ。

アニメーション付きのナビバーを作ってみました、簡単でした。やったね。

基本的な作り方

  • location.hash を監視して情報更新
  • hashに該当する内容を表示
  • バー位置をhashの候補の順序から算出

簡単に作れたは良いんだけど、最後のやつどうしようかなと。

バー位置をhashの候補の順序から算出

最初に書いたコードはこう。

<nav>
  <a href="#">Home</a>
  <a href="#about">About</a>
  <a href="#contact">Contact</a>
  <span :style="underlineStyle"></span>
</nav>
const store = require('./store.js')

const hashes = ['', '#about', '#contact']

module.exports = {
  data () {
    return store.state
  },
  computed: {
    underlineStyle () {
      const itemWidth = 100
      const left = itemWidth * hashes.indexOf(this.hash)
      return {
        transform: 'translateX(' + left + 'px)'
      }
    }
  }
}

これで全然動くんだけど、疑問点が二つ。

  • 候補値 '', '#about', '#contact' をHTML側と共有しているの、どうにかならんかな
  • 項目の幅 100 をCSS側と共有しているの、どうにかならんかな

jQueryであれば実際の要素を見てあれこれするんだけど、Vueはそうはしないじゃないすか。普通。

$refs を使う?

とか言いつつ要素を見てあれこれするやつ、使ったらできるにはできた。

<nav ref="list">
  <a href="#">Home</a>
  <a href="#about">About</a>
  <a href="#contact">Contact</a>
  <span :style="underlineStyle"></span>
</nav>
computed: {
  /**
   * 項目の幅の実測値を返す。
   */
  itemWidth () {
    const elList = this.$refs.list
    const elItem = elList.firstElementChild
    return elItem.clientWidth
  },

  /**
   * 項目の `href` からhash候補値を得る。
   */
  hashes () {
    const elList = this.$refs.list
    const elItems = elList.children
    const hashes = Array.from(elItems).map(elItem => {
      let hash = elItem.getAttribute('href')
      if (hash === '#') {
        hash = ''
      }
      return hash
    })
    return hashes
  },

  underlineStyle () {
    let left

    // 最初のDOM構築の際には当然underlineStyleは呼ばれるが、
    // 最初だからDOMがまだないので、 `$refs` が使えない。
    if (this.$refs.list) {
      left = this.itemWidth * this.hashes.indexOf(this.hash)
    } else {
      // `hash` 変更時にキャッシュ値を更新するよう、
      // ここで呼んで記憶してもらう
      left = this.hash.length * 0
    }

    return {
      transform: 'translateX(' + left + 'px)'
    }
  }
},

うわあ、すごく危険な香りがする。

実際公式ガイドにもやめてねって書いてあるし。

$refs はコンポーネントが描画された後にのみ追加されます。そしてそれはリアクティブではありません。直接子コンポーネントを操作するための最終手段としての意味しかありません。テンプレートまたは算出プロパティの中での $refs の使用は避けるべきです。

うん。

テンプレートまたは算出プロパティの中での $refs の使用は避けるべきです。

そう思います。

マウントのタイミングで確認したい……

……マウント……ライフサイクル……ん?

module.exports = {
  data () {
    return {
      itemWidth: 999,
      hashes: [],
      state: store.state
    }
  },
  mounted () {
    const elList = this.$refs.list
    const elItems = elList.children

    this.itemWidth = elItems[0].clientWidth

    this.hashes = Array.from(elItems).map(elItem => {
      let hash = elItem.getAttribute('href')
      if (hash === '#') {
        hash = ''
      }
      return hash
    })
  },
  computed: {
    underlineStyle () {
      const left = this.itemWidth * this.hashes.indexOf(this.state.hash)
      return {
        transform: 'translateX(' + left + 'px)'
      }
    }
  }
}

なるほど、こういう感じか。これなら良さそう。