【jsステップアップ】[6]拡大(ズーム)機能を作成する②

09/04/2019

拡大機能を追加する。の続きです

現場で役立つjs実装サンプル。として初心者の方に向けての連載6回目です。
今回で一応最終※です。前回は拡大機能追加のjavascriptでイベント系のメソッド中心に説明しましたが、今回はupdateの処理等をメインに見ていきます。

【拡大機能付きスライダー】javascript実装サンプルの連載一覧

  1. [1]初心者の方向けに作成手順や説明等していきます
  2. [2]ドラッグ値/スワイプ値を取得する
  3. [3]スライダーを作成する
  4. [4]モーダルを作成する
  5. [5]拡大(ズーム)機能を作成する
  6. [6]拡大(ズーム)機能を作成する②

モーダルに拡大(ズーム)機能を追加したサンプル

サンプルは前回と同じですが、一応貼っておきます。

ズーム機能を追加するjavascript②(拡大基準点の変更やドラッグ等での移動)

前回はZoomクラスで利用するプロパティやイベント系等メインに説明したので今回は、requestAnimationFrame(updarte)で毎フレーム更新される
や、多分一番わかりにくい所な気がする拡大基準点の変更に内容について説明します。

ではさっそくですが、メソッド毎の補足をしていきます。

addDiffPoint()

addDiffPoint() {
        const addPoint = new Point(EventTouch.instance.diffPoint.x / this.baseSize, EventTouch.instance.diffPoint.y / this.baseSize);
        this.targetPoint = new Point(this.downPoint.x - addPoint.x, this.downPoint.y - addPoint.y);
}

呼び出し元はmousemoveだったりtouchmoveです。
ドラッグとかで移動する移動量を決めています。

EventTouch.instance.diffPoint

スライダーの時にも登場しましたが、
diffPoint自体にはpx単位の数値が入っています。
なので前回補足したnormalizePoint()と同じように
baseSizeと組み合わせて正規化した後の差分をtargetPointに入れています。

updateOrigin()

ここが多分一番ややこしい、と個人的に思います。
実際昔やった仕事のコードひっぱりだして今回の一連の記事かいてみたのですが、
自分で書いたコードなのだけど、久々みるとどういう処理だっけ?となりました。

のでちょっとあとまわし

update()

requestAnimationFrameでの毎フレームの処理です。

this.moveArea();
this.easing();
this.addStyle();
this.updateInfo();

ちょっと記述多くなってしまうので小分けにしましたが
要は上記のメソッドが毎フレーム実行されてます。

moveArea()

moveAreaはドラッグ等で移動できる距離(範囲)を決めています。
拡大時は拡大要素サイズが表示領域を上回るので、移動して見れるようにします。
scaleが1.0の時は拡大要素サイズ=表示領域なので移動はできないようにします。

this.areaLeftMin = this.originPoint.x - (this.originPoint.x * this.currentScale);
this.areaRightMax = -((1.0 - this.originPoint.x) - (1.0 - this.originPoint.x) * this.currentScale);
this.areaTopMin = this.originPoint.y - (this.originPoint.y * this.currentScale);
this.areaBottomMax = -((1.0 - this.originPoint.y) - (1.0 - this.originPoint.y) * this.currentScale);

少し分かりにくいのが、拡大の基準点であるoriginPoint(transform-originでスタイル付加)が
変わると、基準位置によって要素が表示上移動するので、
移動できる範囲値も変わるという所、

easing()

// 位置のイージング処理
if (this.targetPoint.x <= this.areaLeftMin)
    this.targetPoint.x += (this.areaLeftMin - this.targetPoint.x) * this.factorNum;
if (this.targetPoint.x >= this.areaRightMax)
    this.targetPoint.x += (this.areaRightMax - this.targetPoint.x) * this.factorNum;
if (this.targetPoint.y <= this.areaTopMin)
    this.targetPoint.y += (this.areaTopMin - this.targetPoint.y) * this.factorNum;
if (this.targetPoint.y >= this.areaBottomMax)
    this.targetPoint.y += (this.areaBottomMax - this.targetPoint.y) * this.factorNum;

this.currentPoint.x += (this.targetPoint.x - this.currentPoint.x) * this.factorNum;
this.currentPoint.y += (this.targetPoint.y - this.currentPoint.y) * this.factorNum;

if (Math.abs(this.currentPoint.x - this.targetPoint.x) < this.fractionNum)
    this.currentPoint.x = this.targetPoint.x;
if (Math.abs(this.currentPoint.y - this.targetPoint.y) < this.fractionNum)
    this.currentPoint.y = this.targetPoint.y;

そのまんまイージングです。イージングの処理としては
スライドとかに出てきたような内容と一緒なのですが、
moveArea()で求めたareaLeftMinとかareaRightMaxの値を超えた
場合に範囲内に収まるようにしつつtargetPointを更新しています。

absやfractionNumを利用している箇所は切り上げ切り下げ等を行っています。

addStyle()

addStyle() {
    // 要素にスタイルやスケール等を反映
    Object.assign(this.zoom__elm.style, {
        "transform": `translate(
      ${-this.currentPoint.x * this.baseSize}px,
      ${-this.currentPoint.y * this.baseSize}px
      )
      scale(${this.currentScale})`,
        "transform-origin": `${this.originPoint.x * this.baseSize}px ${this.originPoint.y * this.baseSize}px`
    });

    this.areaScale = 1.0 / this.currentScale;
    Object.assign(this.zoom__area.style, {
        "transform": `translate(
      ${this.currentPoint.x * this.areaSize * this.areaScale}px,
      ${this.currentPoint.y * this.areaSize * this.areaScale}px
      )
      scale(${this.areaScale})`,
        "transform-origin": `${this.originPoint.x * this.areaSize}px ${this.originPoint.y * this.areaSize}px`
    });

    Object.assign(this.zoom__origin01.style, {
        "transform": `translate(
      ${this.originPoint.x * this.baseSize}px,
      ${this.originPoint.y * this.baseSize}px
      )`
    });
    Object.assign(this.zoom__origin02.style, {
        "transform": `translate(
      ${this.originPoint.x * this.areaSize}px,
      ${this.originPoint.y * this.areaSize}px
      )`
    });
}

色々求めた数値で諸々の要素スタイルを更新します。
やってる事はtranslate/scale/transform-origin等の
cssスタイルを毎フレーム要素に反映しているだけです。

baseSize/areaSize

一点補足するとthis.baseSizeっていうのは、

各ポイントは(currentPointとかoriginPoint)0.0~1.0ベースの数値になっているので
要素に反映する場合は、normalizePoint()等で行った処理と逆に*this.baseSizeで実際のpx等に戻した値を入れます。

areaSizeも同じく確認エリアのサイズなので、
正規化した数値*areaSize
で実際のpx値に変換します。

updateInfo()

これは、ただのデバッグ用なので、おまけです。
ポイント系の数値をinnerHTMLでテキストとして更新します。

updateOrigin()

後回しにしてたupdateOrigin()

拡大基準点が変わるとmoveArea() でも触れましたが、要素が表示上ガタっと
移動したように見えてしまいます。
なのでその辺りを表示上は変わってないように見せつつ基準点を更新します。

updateOrigin() {
    // 拡大縮小の基準点設定
    const tempOriginPoint = new Point(this.originPoint.x, this.originPoint.y);

    const areaLeft = this.originPoint.x * (this.currentScale - 1.0) / this.currentScale + this.currentPoint.x * this.areaScale;
    const areaTop = this.originPoint.y * (this.currentScale - 1.0) / this.currentScale + this.currentPoint.y * this.areaScale;

    const originX = areaLeft + this.pointerPoint.x * this.areaScale;
    const originY = areaTop + this.pointerPoint.y * this.areaScale;
    this.originPoint = new Point(originX, originY);

    const diffPoint = new Point(this.originPoint.x - tempOriginPoint.x, this.originPoint.y - tempOriginPoint.y);

    diffPoint.x = diffPoint.x - (diffPoint.x * this.currentScale);
    diffPoint.y = diffPoint.y - (diffPoint.y * this.currentScale);

    this.currentPoint.x = this.targetPoint.x = (this.currentPoint.x + diffPoint.x);
    this.currentPoint.y = this.targetPoint.y = (this.currentPoint.y + diffPoint.y);
}

tempOriginPoint

tempOriginPointは拡大基準点変更前の位置を記録します。
あとで差分とる用。

areaLeftとareaTop

areaLeftとareaTopは
originX/Yへの代入で利用するのですが、
現在の表示されている表示領域のleftとtopの位置を入れてます。
(実際ブラウザ上で見えている場所の左上)

originXとoriginY

mousePointは要素のベースサイズに対しての0.0~1.0の値なので
そのまま入れても拡大時には基準点の設定が上手くできません。
※基準点はPCの場合、表示された拡大要素のマウス位置、が普通かと思います。

ので上でもとめた (areaLeft&areaMin)+mousePoint*areaScaleです。

diffPoint

ガタっと移動(したように)見えるのを防ぐ差分です。

currentPoint/targetPoint

currentPoint/targetPointはそのままにするとイージングで緩やかに更新されるので、基準点が変わった時は上で求めた差分を追加した。位置に更新します。
位置更新する事でガタつきは解消されます。

【拡大機能付きスライダー】javascript実装サンプル。以上です。

人に説明したりは不慣れなもので、
結構わかりにくい所もあるかもで恐縮です。
追々各所に追記したりもう少しまとめてみます。

が、一応これで本連載は終わりです。
とりあえずざっと一通り補足入れていきましたが、
時間ある時に見直したりして色々整理していこうとは思っています。

参考になりましたら幸いでございます。

【拡大機能付きスライダー】javascript実装サンプルの連載一覧

  1. [1]初心者の方向けに作成手順や説明等していきます
  2. [2]ドラッグ値/スワイプ値を取得する
  3. [3]スライダーを作成する
  4. [4]モーダルを作成する
  5. [5]拡大(ズーム)機能を作成する
  6. [6]拡大(ズーム)機能を作成する②

本連載に関して

本連載では、初心者向けに各所の解説をしていますが、
一歩踏み込んだ実践的な内容にはなっていると思います。
※フロントエンド(HTML/CSS/JavaScript)の基礎知識は必須になります。