無職です。はたらいてます。 旧ブログ: 1 Entry per Day

input[type=range]で範囲指定っぽいUI

f:id:mstssk:20200518231950p:plain

input[type=range]要素を組み合わせると、なんちゃって範囲指定のUIが構築できそうなのでやってみた。

始値を指定するスライダーと、終了値を指定するスライダーが連動して、開始値が終了値より先にいったりしないよう制限もかけている。

developer.mozilla.org

実際に動作するサンプルはStackblitzで作ったページを参照: https://react-ts-keurq5.stackblitz.io

stackblitz.com

大事なのはdatalist要素で範囲を定義しておくこと。 単純にinput[type=range]要素でmax属性とmin属性で最大値・最小値または連動するように互いのvalueを参照し合うよう指定しても、想像した任意の値の範囲を指定するようなUIにはならない。 正確に言えば、開始値が終了値を追い越さないようになっているし開始値と終了値を指定する機能自体は満たしているけど、スライダーの値の範囲がぐちゃぐちゃ可変してしまって使いづらい。

そこで、datalistで値の範囲を定義して、2つのinput[type=range]要素がどちらも同じ値の範囲を持つスライダーになるようにした。 開始値のスライダーが終了値のスライダーより大きい値にならないように仕込むには、max,min属性を使っていけなくて、onChangeイベントで引っ掛ける必要があった。

以下にコードを貼り付けておく。 今回はReactで作ったが、やっていることは単純なので、他のフレームワークや素のJavaScriptでもすぐ書ける。

import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";

interface AppProps {
  startAt: number;
  endAt: number;
  callback?: (range: AppState) => any;
}
interface AppState {
  startAt: number;
  endAt: number;
}

class App extends Component<AppProps, AppState> {
  constructor(props) {
    super(props);
    this.state = {
      startAt: this.props.startAt,
      endAt: this.props.endAt
    };
  }

  render() {
    const onChangeStart = (event: React.ChangeEvent<HTMLInputElement>) => {
      const startAt = parseInt(event.target.value);
      // 終了値を超えないように
      if (startAt < this.state.endAt) {
        this.setState({ startAt }, () => {
          this.props.callback(this.state);
        });
      }
    };

    const onChangeEnd = (event: React.ChangeEvent<HTMLInputElement>) => {
      const endAt = parseInt(event.target.value);
      // 開始値を下回らないように
      if (endAt > this.state.startAt) {
        this.setState({ endAt }, () => {
          this.props.callback(this.state);
        });
      }
    };

    // option要素で値の範囲を定義しないと、範囲が固定にならない。
    const options = [];
    for (let i = this.props.startAt; i <= this.props.endAt; i++) {
      options.push(<option key={i} value={i} />);
    }

    return (
      <div>
        <h1>input[type=range]で範囲指定っぽいUI</h1>
        <p>スライダーをドラッグして開始と終了をそれぞれ指定する</p>
        <datalist id="datalist">{options}</datalist>
        <p>
          値の範囲: {this.state.startAt}{this.state.endAt}
        </p>
        <div>
          開始:&nbsp;
          <input
            type="range"
            list="datalist"
            min={this.props.startAt}
            max={this.props.endAt}
            value={this.state.startAt}
            onChange={onChangeStart}
          />
          <br />
          終了:&nbsp;
          <input
            type="range"
            list="datalist"
            min={this.props.startAt}
            max={this.props.endAt}
            value={this.state.endAt}
            onChange={onChangeEnd}
          />
        </div>
      </div>
    );
  }
}

const logging = value => {
  console.log(value);
};

render(
  <App startAt={0} endAt={120} callback={logging} />,
  document.getElementById("root")
);

stackblitz上のソースコードhttps://stackblitz.com/edit/react-ts-keurq5?file=index.tsx

ルンバe5を買ってしばらく経ったのでまとめ

f:id:mstssk:20200511233053j:plain

TL;DR

 

  • 明るい色のラグを買え。

 

 

2月くらいにルンバe5を購入しました。基本的に満足しているのですが、いくつか課題もあったのでまとめてみます。

www.irobot-jp.com

 

ラグ・絨毯の色には気をつけろ

ルンバを買ったのと近い時期に暗めの色のラグを買ってしまうという、ルンバビリティを一番下げるミスをしてしまいました。 そして買って早々に落下センサー部をアルミホイルで隠すというハックをする羽目に。

ラグの色はこんな感じです。このくらいの色でもルンバは段差だと認識しちゃうようです。

f:id:mstssk:20200511233050j:plain
暗めのブラウンなラグ

センサーの隠し方は少し試行錯誤のした結果、こういう形に落ち着いています。

  • 幅1cmくらいのアルミホイルで覆うだけ。横からは光が入るようになっていないといけない。
    • センサーを完全に隠してはいけない。真っ暗になり常に段差の上にいる判定になりルンバが動かない。
  • お手入れの際にセンサー部をどのご家庭にもあるエアダスターで掃除する。
    • 覆った分、ホコリ等が挟まって汚れやすくなる。汚れて暗くなり段差と誤検知することもある。わざと残した横の隙間からエアダスターでホコリを吹き飛ばす。

f:id:mstssk:20200511233045j:plain
センサー部を1cm幅のアルミホイルで覆って上から黒いマスキングテープで固定している。手前にわざと隙間を残す。4つ のセンサーそれぞれ同じ様に覆う。

もちろん、段差検知のためのセンサーを隠しているので、段差に落ちるようになります。 うちの場合、玄関の段差に落ちるようになったので、バーチャルウォールで回避しています。

あと、センサーを隠しているせいなのかいまいちわかりませんが、Dirt Detectが動作していない気もします。2ヶ月立ちますが、一度もありません。我が家がそこまで汚くないだけなのかどうか…

 

1,2cmちょっとの段差なら乗り越える

ので、越えてほしくないところに物を置いたりしないように。

カラーボックスの1番下の段にビニール紐を置いていたら、そこにルンバが突っ込んで巻き込んで止まってました。

また、こういうリクライニングチェアの足に乗り上げて止まってしまうのも想定外でした。 今は足に黒い紐を張ってルンバが乗り上げないように回避しています。

 

バーチャルウォールよりそもそも床に物を置かないようにする

バーチャルウォールが充電ベースに向いていなくても、近くにあると干渉することがあるようです。ルンバがいまいちベースに帰ってくれない場合、バーチャルウォールの位置を変えてみるとよいと思います。

ホームベースから2.5 m以内に デュアルバーチャルウォールや他のルンバのホームベースなどが設置されていないことを確認してください。これらの機器の赤外線が干渉する場合があります。 ルンバがホームベースに戻らない。

そもそもバーチャルウォールをできるだけ使わないほうがよいと思っています。 とはいえ棚が足りないとか配線の都合で電源タップや一部機器が床置きになっているので、無印良品のワイヤーバスケットにとりあえず入れることでルンバを回避したりしています。

18ー8ステンレスワイヤーバスケット2 約幅37×奥行26×高さ8cm 通販 | 無印良品

 

家にいる時に動かさないほうがよい

購入後は平日昼間の会社にいる最中にルンバに動いてもらうようスケジュール設定していました。 しかし最近は自宅勤務が続いています。

ルンバが動いている最中も気にせず仕事したりしていたんですが、ラグの上を掃除する時に流石にホコリがそれなりに舞うようで、咳き込むようになりました。

今は買い物やジョギングのため外出する時に動かすようにしています。 Googleアシスタントの外出時ルーティンにカスタムコマンドで「掃除して」と仕込んでいます。

Googleアシスタントのルーティンのカスタムコマンドの設定画面で「掃除して」と入力して保存する

全日本ラジオボタンのラベルにクリック判定を付けろ協会

f:id:mstssk:20200507202238p:plain

全日本ラジオボタンのラベルにクリック判定を付けろ協会 会長の id:mstssk です。

皆さんはチェックボックスラジオボタンのラベル(キャプション)をクリックしても選択したことにならず憤慨したことはありませんか?
私はあります。

(追記)タイトルはラジオボタンなのに記事はチェックボックス主体で書いているのはただのミスです😇

よくないチェックボックス

Webサイトでこういうチェックボックスによる選択肢をよく見かけます。

選択肢1
選択肢2
<input type="checkbox"> 選択肢1
<input type="checkbox"> 選択肢2

そして大抵の場合、視覚的にチェックボックスと一体になっている「選択肢x」というラベルをクリックしても何も起きないということがよくあります。

こういう実装はアクセシビリティの問題があります。
まず、単純にチェックボックスを選択しづらいということ。
そして、チェックボックスとそのラベルの一体というセマンティクスをHTML上表現していないため、視覚障害者などの読み上げツールから適切に認識されません。

よいチェックボックス

チェックボックスとラベルのセットを表現するには<label>要素を使います。 HTMLだけで実現できます。 JavaScriptCSSもいりません。

developer.mozilla.org

<input>要素を<label>要素でくくるだけでOKです。

<label><input type="checkbox"> ラベルをクリックしてもチェックできる</label>

<input>要素のclickイベントで何かしら処理を走らせている場合でも問題ありません。ラベル部分をクリックしても<input>要素のclickイベントは発火します。

デザインなどの都合により<label>要素でくくれない場合、for属性とid属性を使います。

<input type="checkbox" id="checkbox01">
<label for="checkbox01">ラベルをクリックしてもチェックできる</label>

チェックボックスだけでなくラジオボタンでも同じことができます。 いくつかのパターンを書いたサンプルページを用意しました。

Macでfn+7をF7にする方法

仕事で使っているMacBook Proのバタフライキーボードのチャタリングが酷すぎる。 Unshakyを使ってみたりいているんですが、緩和策でしかないのでやっぱり誤入力が度々起こってしまい、どうしようもない。

HHKBあたりを衝動的に買ってしまおうかと思ったのですが、fn+数字キーによるFunctionキー入力が少々ネックだと感じて、Amazonの購入確定ボタンをまだ押していません。 *1

普段日本語を入力する際にF7キーでカタカナ変換したりなどFunctionキーをよく使っているので、個人的にけっこう大きな課題です。 fn+7の入力に慣れたとしても、今度はデスクを離れてバタフライキーボードを使わざるを得ない状況で戸惑いそうだな、とか買う前から無駄に悩んでいます。

Karabinerでなんとかする

いっそバタフライキーボード使っている時を含めて普段からfn+数字キーで入力する状況に自分を矯正してしまえばいいのでは?と考えました。

KarabinerというMacのキー入力を自由にカスタマイズできるツールで対応できます。 *2

karabiner-elements.pqrs.org

インストールガイドに従い、インストールとMacのシステムの入力監視への操作権限を付与したら、使えるようになります。

  1. Karabinerのサイトでfn+数字キーの置換ルールを検索
  2. Map fn + number keys to function keys (rev 2) というルールセットをImport
  3. インポート後のKarabinerの設定画面が開くので、 Map fn + number keys to their corresponding function keys (rev 1) をEnable

fn+数字キーをFunctionキーに置き換える設定を選択している画面キャプチャ

輝度設定やメディア再生コントロールが動作しちゃう場合

Macのキーボード設定で「F1、F2などを標準のファンクションキーとして使用」を有効にしていると、 Map fn + number keys to function keys (rev 2) ルールではFunctionキー入力になってくれません。

替わりに Map fn + number keys to their corresponding media control keys (rev 2) をEnableにすればちゃんとfn+数字キーがFunctionキー入力になってくれます。

「F1、F2などを標準のファンクションキーとして使用」を有効にしている画面キャプチャ

設定のJSONファイルの中身を見ると理由がわかります。 このルールが実際に行っているのは次のとおりです。

  • Map fn + number keys to their corresponding function keys (rev 1):
    • fn+1 → F1+fn
  • Map fn + number keys to their corresponding media control keys (rev 2):
    • fn+1 → F1

つまり、「F1、F2などを標準のファンクションキーとして使用」が有効になっていない状態のキーマップを想定した置換ルールだからでした。

追記: 別のショートカットキー

ctrl+i に体を矯正したほうがよさそう。

*1:後から改めて考えたら、自分はHHKBが欲しいのではなくMacで使えるまともなキーボードが欲しいだけなので、Magic Keyboardでもええんちゃうかと考えています。

*2:ありがとう、ようてんさん

video要素のcontrols属性のブラウザごとの動作

2020/03/22現在はほぼ全てのブラウザでvideo要素のcontrols属性をサポートしている。

でもどうせブラウザによって動作が違うんでしょ、と思って調べました。

https://caniuse.com/#feat=mdn-html_elements_video_controls

f:id:mstssk:20200322231108p:plain
video要素のcontrols属性のブラウザ対応表

サンプルコード

動作確認のため作ったページ: Example:`controls` attr of video element.

動画は🍏の最近のSplatoon 2のプレイ映像。

github.com

スクリーンショット

左上からZ字の順でSafari, Firefox, Chrome, Edge。

初期表示 再生して1秒後の表示
f:id:mstssk:20200322231245p:plain f:id:mstssk:20200322231240p:plain

各ブラウザのコントロール表示

ブラウザ別の機能差分表

環境: macOS Catalina 10.15.3

  Chrome 80.0.3987.149 Firefox 74.0 Safari 13.0.5
再生/一時停止ボタン あり あり あり
音量コントロール あり あり あり
全画面表示ボタン あり あり あり
再生時間表示 現在/総再生時間 現在/総再生時間 現在/再生時間
Picture in Picture あり(ハンバーガーメニューから
Android ChromeではN/A)
あり(コンテキストメニューから) あり
15秒進む/戻るボタン N/A N/A あり
Chromecast等へのキャスト あり(コンテキストメニューから
Android Chromeではハンバーガーメニューから)
N/A N/A
ダウンロード
※"名前を付けて保存"といったメニューではなくあくまで「ダウンロード」メニューの有無
あり(ハンバーガーメニューから) N/A あり(コンテキストメニューから)
再生速度変更 N/A あり(コンテキストメニューから) N/A
コントロールの表示/非表示切り替え あり(コンテキストメニューから) あり(コンテキストメニューから) あり(コンテキストメニューから)
video要素クリック 再生/一時停止トグル 再生/一時停止トグル 再生/一時停止トグル
video要素フォーカス時スペースキー押下 再生/一時停止トグル 再生/一時停止トグル N/A
video要素フォーカス時左右カーソルキー押下 押している間、早回し/早戻し 押すと15秒進む/戻る N/A
  • Microsoft EdgeChromeと同じ動作だったので対応表では割愛。
  • ※video要素へのクリック・キーボード操作はいずれのブラウザもコントロール表示中のみ有効。
  • Firefoxはマウスホバー時のみコントロールを表示する動作だった。
  • Android ChromeAndroid 10のPixel 3上での同バージョン。

真似しちゃいけないdeployステップの使い方

これはCircleCI Advent Calendar 2019の8日目の記事です。

qiita.com


こんにちは、CircleCI大プッシュしてたらCircleCIの回し者だと思われた@mstsskです。

今回はdeployステップの変な使い方を思いついたので紹介します。

ただし、真似しちゃいけません。

deployステップについて

CircleCIでは、CIジョブで実行するコマンドのステップをrunというキーワードで記述します。 そして実はdeployというキーワードでもコマンドを記述できるようになっています。

deployステップの書き方はrunと同じですが、文字通りデプロイのためのステップであり、動作が異なります。

parallelism を使ったジョブの場合、deploy ステップは他のすべてのノードが成功した場合にのみ、ノード番号 0 として実行されます。 ノード番号 0 以外はステップを実行しません。

引用元: https://circleci.com/docs/ja/2.0/configuration-reference/#deploy

deployステップを使ったconfig.ymlは次のような感じになります。

# .circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/ruby
    parallelism: 4 # 4並列で実行
    steps:
      - checkout
      - run: bundle install
      - run: # circleciコマンドを使ってテストを4分割して実行
          name: Parallel Testing
          command: bundle exec rspec $(circleci tests glob spec/**/*.rb | circleci tests split)
      - deploy: # テストが通ったらデプロイ。このステップは4つのノードのうち0番目でだけ実行される
          name: Deploying to dev env
          command: bundle exec cap dev deploy

つまり、deployステップを使うとCircleCIのジョブの中で並列処理の待ち合わせができるんです!

待ち合わせ処理を書いてみる

並列実行している処理の待ち合わせが書けるなら、あとはデータの受け渡しができれば、もう並列処理のプログラム書けるようなもんだよね? 例えば、大きな処理の分割実行とその結果の統合が1つのジョブの中で済ませられるのでは?と考えてやってみました。

deployステップで並列実行の待ち合わせをして、各ノードの結果をCircleCIのキャッシュ経由で受け渡しするサンプルは次のとおりです。

# .circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/node:12
    parallelism: 4
    steps:
      - checkout

      # 成果物を result-{ノード番号}.txt に保存する、ビルドとかテスト実行など並列実行したい遅い処理
      - run:
          name: Building Some Results
          command: |
            mkdir results/ &&
            ./dummy_slow.sh > results/result-$CIRCLE_NODE_INDEX.txt

      # 成果物をキャッシュに保存
      - save_cache:
          key: results-{{ .BuildNum }}-{{ .Environment.CIRCLE_NODE_INDEX }}
          paths:
            - results/

      # 各ノードのビルド実行・キャッシュ保存を待つ
      - deploy:
          name: Waiting Results
          command: ":"

      # 各ノードの成果物をキャッシュから取り出す
      - restore_cache: { keys: ["results-{{ .BuildNum }}-1"] }
      - restore_cache: { keys: ["results-{{ .BuildNum }}-2"] }
      - restore_cache: { keys: ["results-{{ .BuildNum }}-3"] }

      # 0番目のノードで他ノードの成果物を組み合わせる
      - deploy:
          name: Merging Results
          command: |
            ls results/
            echo
            cat results/result-*.txt

restore_cache のところがどうしても冗長な書き方になりますが、これで1つのジョブの中で並列実行した結果を統合できます。

もうちょっとconfig.ymlを整理したサンプルは↓。

https://circleci.com/gh/mstssk/circleci-deploy-step-play github.com

何に使うの?

もともとは、並列テストのカバレッジデータをサクッと統合したいと思ってなんとかジョブの中でミニマムに解決できないかと考えて思いついたものです。

実際にジョブの中でデータ集めができることを確認したあたりで正気に帰りました。 本来の使い方とは違うやり方だけど試行錯誤してなんとかなったぜやったぁ状態になる瞬間ってたまにありますよね。

よっぽど1つのジョブの中で完結させたい理由がもしあったなら使えるテクニックかもしれませんが、本当に必要かどうか3回考えてから諦めましょう。 普通に別ジョブにしてpersist_to_workspaceでデータ受け渡してやりましょう。

カバレッジデータの統合をしたい場合は、各カバレッジツールがCircleCIの場合の使い方サンプルを公開してたりしますし、Coverallsは結果をよしなにマージしてくれる機能があるらしいです1

width属性の詳細度は要素セレクタよりも低い

img要素などいくつかのHTML要素では widthheight を属性で書いてもよい。

<img width="100px" src="foobar">

しかし、このwidth属性に対してCSSでもwidth指定をしていたら、どちらが優先されるのか。 <img width="100px"><img style="width:100px;"> は挙動が違うというのはわかって入るが、具体的にどうなんだろうと思って調べてみた。

ざっくり調べた結論としては、属性でwidthを指定するのは何よりも優先度(詳細度)が低いというつもりでいればいいだろう、という感じ。

width属性はCSSじゃないので詳細度という言い方が適切かはわからない。

あと、スタイルと一緒くたにしてしまったが、本来はスタイルの指定に使うものではない。 HTML5.2のspecを一部読んだ感じでは、width属性とheight属性両方とも同時に指定するのが本来の仕様であり、ユーザーエージェント(ブラウザ)はその値を描画のヒントとすることを期待するものとのこと。

実際のブラウザの挙動を確認するのはstackblitzで適当にサンプルコード書いた。 Safari, Chrome, Firefoxあたりで確認して、同じだった。

https://stackblitz.com/edit/attribute-style-specificity

参考