見出し画像

マークダウンエディタつくろう その3(サイドバーを動かせるようにする)

というわけで、すでに出来上がってるMDエディタ
(↑の画像は実際のアプリの画像です)

今回はサイドバー(っていう呼び方なのかな?)
マウスでドラッグして枠の幅などを変える機能。
実はwebでやると地味に面倒。
今のバージョンでも実装はできてるんだけど、
今回は複数個所やるので共通化をしてみた。

実装で特に気にしなきゃいけないのがマウスのドラッグ動作。
ここがおかしいと変な挙動になる。

やることとしては・・・

  1. マウスのup,move,downのイベント制御してドラッグだけを実現するクラスの作成

  2. 1のクラスを使って対象のElementを操作する処理を入れる

ということで1のドラッグを管理するクラスの実装

//tsファイル

/**
 * マウスドラッグを制御するクラス
 */
export class drag<T extends unknown[]>{

    private element:HTMLElement;
    private fMdown = this.mouseDown.bind(this);
    private fMup   = this.mouseUp.bind(this);
    private fMmove = this.mouseMove.bind(this);
    
    private fDownCallback: (e:MouseEvent,...args: T) => void = () => {};
    private fDownArgs: T | null = null;

    private fMoveCallback: (e:MouseEvent,...args: T) => void = () => {};
    private fMoveArgs: T | null = null;

    private fUpCallback: (e:MouseEvent,...args: T) => void = () => {};
    private fUpArgs: T | null = null;

    private isMove:boolean = false;
    

    constructor(element:HTMLElement){
        this.isMove  = false;
        this.element = element;
        this.element.addEventListener("mousedown", this.fMdown, false);
    }

    /**
     * mousedown時のコールバック関数をセットする
     * @param callback コールバック関数
     * @param args コールバック関数の引数
     */
    onDown(callback: (e:MouseEvent,...args: T) => void, ...args: T): void {
        this.fDownCallback = callback;
        this.fDownArgs = args;
    }

    /**
     * mousemove時のコールバック関数をセットする
     * @param callback コールバック関数
     * @param args コールバック関数の引数
     */
    onMove(callback: (e:MouseEvent,...args: T) => void, ...args: T): void {
        this.fMoveCallback = callback;
        this.fMoveArgs = args;
    }

    /**
     * mouseup時のコールバック関数をセットする
     * @param callback コールバック関数
     * @param args コールバック関数の引数
     */
    onUp(callback: (e:MouseEvent,...args: T) => void, ...args: T): void {
        this.fUpCallback = callback;
        this.fUpArgs = args;
    }
    

    private mouseDown(event:MouseEvent){
        event.preventDefault();
        this.element.classList.add("drag");

        document.body.addEventListener("mousemove", this.fMmove, true);  //1

        this.element.addEventListener("mouseup", this.fMup, false);  //2
        document.body.addEventListener("mouseup", this.fMup, false);    //3
        document.body.addEventListener("mouseleave", this.fMup, false); //4

        if(this.fDownCallback && this.fDownArgs) this.fDownCallback(event, ...this.fDownArgs);
    }

    private mouseMove(event:MouseEvent){
        event.preventDefault();

        console.log("mousemove");
        
        if(document.getElementsByClassName("drag")[0] != null){
            const dragElement = <HTMLElement>document.getElementsByClassName("drag")[0];

            if(!this.isMove){
                dragElement.addEventListener("mouseup", this.fMup, false);  //2
                document.body.addEventListener("mouseup", this.fMup, false);    //3
                document.body.addEventListener("mouseleave", this.fMup, false); //4
                this.isMove = true;
            }  
    
        }else{
            
            document.body.removeEventListener("mousemove", this.fMmove, true);  //1
        }

        if(this.fMoveCallback && this.fMoveArgs) this.fMoveCallback(event,...this.fMoveArgs);

    }

    private mouseUp(event:MouseEvent){
        console.log("mouseUP");
        event.preventDefault();
        const dragElement = <HTMLElement>document.getElementsByClassName("drag")[0];

		//ムーブベントハンドラの消去
        document.body.removeEventListener("mousemove", this.fMmove, true);  //1

        //upイベントの消去
        document.body.removeEventListener("mouseup", this.fMup, false); //3
        document.body.removeEventListener("mouseleave", this.fMup, false);  //4
		
        //if(dragElement != undefined){
			dragElement.removeEventListener("mouseup", this.fMup, false);   //2
            dragElement.classList.remove("drag");
        //}

        if(this.fUpCallback && this.fUpArgs) this.fUpCallback(event,...this.fUpArgs);
 
        this.isMove = false;
    }

    private removeEvent(){
        document.body.removeEventListener("mousemove", this.fMmove, true);  //1
    }


}

簡単な解説を。
onDown,onMove,onUpはコールバック関数を指定してもらい、マウスのクリック、動かす、ボタンを離す時の動作をここで設定。
他のプライベートメソッドはそれぞれのアクションの際にイベントリスナーを制御とコールバック関数を呼ぶ(発火させる)メソッドとなってます。

で、これを使う2についてはこんな感じ

//動かす対象のElementを取得(ここではサイドバーのDivを取得)
 const menuBorder = document.getElementById("menuBorder");
 const menuBorderRect = {
     x:0,
     y:0
 };

 const menuBorderDrag = new drag(<HTMLElement>menuBorder);

//マウスがクリック(押された状態)になった時
 menuBorderDrag.onDown((e) =>{
     const event = <MouseEvent>e;
     const element = <HTMLElement>menuBorder;

     menuBorderRect.x = event.pageX - element.offsetLeft;
     menuBorderRect.y = event.pageY - element.offsetTop;

 });

//マウスがボタンが押された状態かつ動かしているとき
 menuBorderDrag.onMove((e) =>{
     const event = <MouseEvent>e;
     document.documentElement.style.setProperty('--menuarea-width', event.pageX - menuBorderRect.x + "px");
 });

マウスがクリック(ボタン押下)されたときは、
してされたElementの位置を保存。
マウスが動いたときは、
メニューの幅をElementの動かした量に応じて幅を変える処理をしてます。

document.documentElement.style.setProperty('--menuarea-width', event.pageX - menuBorderRect.x + "px");

↑ここなんだけど、これは直接幅を変えたいところのwitdhを指定しないで
cssのカスタムプロパティを一か所変更することで連動して他のところの位置や幅が変わるようにしてます。

動画が一番見やすいのだけれど、時間がなかったので画像で説明(わかりにくくてすみません)

サイドバー動かす前


緑と紫の間にサイドバーがあってそれを右にドラッグした図
紫のボックスの位置や白いボックスの幅が連動して変わる

ということでサイドバー処理はここまで(見た目等はまた後で)

次はローカルファイルの処理かなー


参考になったサイト


いいなと思ったら応援しよう!