1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptAdvent Calendar 2024

Day 17

Canvas要素でカレンダーアプリを作ってみる#3

Last updated at Posted at 2024-12-05

はじめに

#2で(下記)、TaskのCreateの関数を実装した続きから行う。(全3記事を予定)

プロジェクトの作成(#2の続きから)

MainとなるCanvas内でイベントを付与する。

  • Canvas内で何もないところにDragしたら、Taskを新規作成する。

Canvas要素でカレンダーアプリを作ってみる3_1.gif

  • Canvas内でTask自体をDragしたら、TaskをDragした最終位置に移動させる。

Canvas要素でカレンダーアプリを作ってみる3_2.gif

  • TaskのResizerをDragしたら、横軸上で伸縮する。

Canvas要素でカレンダーアプリを作ってみる3_3.gif

main.js

/**
 * TaskObjectの新規追加するとき、Objectを仮表示する関数
 * @param {number} x - 位置x
 * @param {number} y - 位置y
 * @param {number} w - width
 * @param {string} c - 表示色
 */
const createtempTask = (x, y, w, c) => {
  blurTask();
  let tempObj = app.stage.getChildByLabel(tempObjLabel);
  if (tempObj) {
    tempObj.destroy({ children: true, texture: true, textureSource: true, context: true });
  }
  let obj = new PIXI.Graphics()
    .rect(x, y, w, objh)
    .stroke({ width: 1, color: c })
    .fill({ color: 0x000000, alpha: 0 });
  obj.label = tempObjLabel + _Graphics;
  obj.interactive = true;
  obj.buttonMode = true;
  // コンテナ作成
  const container = new PIXI.Container();
  container.zIndex = 5;
  container.label = tempObjLabel;
  container.my_x = x;
  container.my_y = y;
  container.my_width = w;
  container.my_color = c;
  container.addChild(obj);
  app.stage.addChild(container);
}

/**
 * TaskObjectの移動するとき、Objectを仮表示する関数
 * @param {string} cursor - 表示カーソル
 * @param {number} x - 位置x
 * @param {number} y - 位置y
 * @param {number} w - width
 * @param {string} c - 表示色
 */
const createtempMoveTask = (cursor, x, y, w, c) => {
  blurTask();
  let tempMoveObj = app.stage.getChildByLabel(tempMoveObjLabel);
  if (tempMoveObj) {
    tempMoveObj.destroy({ children: true, texture: true, textureSource: true, context: true });
  }
  let obj = new PIXI.Graphics()
    .rect(x, y, w, objh)
    .fill(c);
  obj.label = tempMoveObjLabel + _Graphics;
  obj.interactive = true;
  obj.buttonMode = true;
  obj.cursor = cursor;
  // コンテナ作成
  const container = new PIXI.Container();
  container.zIndex = 5;
  container.label = tempMoveObjLabel;
  container.my_x = x;
  container.my_y = y;
  container.my_width = w;
  container.my_color = c;
  container.addChild(obj);
  app.stage.addChild(container);
}
app.stage.on('pointerdown', (e) => {
  if (draggingTask) return; // ObjectをDrag中ならReturn
  if (resizingTask) return; // ObjectをResize中ならReturn
  createStart = { x: -1, y: -1 };
  // 新しいオブジェクトの作成開始位置をグローバル座標で保存
  const currentPos = e.data.global;
  createStart = { x: currentPos.x, y: currentPos.y };
});

app.stage.on('pointermove', (e) => {
  // ポインタの現在の位置を取得
  const currentPos = e.data.global;
  // ObjectをDrag中
  if (draggingTask) {
    let myObj = app.stage.getChildByLabel(draggingTask.label);
    if (myObj) {
      myObj.destroy({ children: true, texture: true, textureSource: true, context: true });
    }
    deletedistinctTask(draggingTask.label);
    createtempMoveTask('move', currentPos.x - draggingTask.my_dragx, currentPos.y - objh / 2, draggingTask.my_width, draggingTask.my_color);
    return;
  }
  // ObjectをResize中
  if (resizingTask) {
    let myObj = app.stage.getChildByLabel(resizingTask.label);
    if (myObj) {
      myObj.destroy({ children: true, texture: true, textureSource: true, context: true });
    }
    deletedistinctTask(resizingTask.label);
    const x = resizingTask.my_x
    const xw = resizingTask.my_x + resizingTask.my_width
    const y = resizingTask.my_y
    if (resizingTask.my_lr == 'R') {
      if (currentPos.x - x >= cellWidth / 12) {
        createtempMoveTask('col-resize', x, y, currentPos.x - x, resizingTask.my_color);
      }
    }
    if (resizingTask.my_lr == 'L') {
      if (xw - currentPos.x >= cellWidth / 12) {
        createtempMoveTask('col-resize', currentPos.x, y, xw - currentPos.x, resizingTask.my_color);
      }
    }
    return;
  }

  if (createStart.x == -1) return;

  // ObjectをCreate中
  if (currentPos.x - createStart.x > 0) {
    // =>方向にCreate
    createtempTask(createStart.x, createStart.y, currentPos.x - createStart.x, defaultcolor);
  } else {
    // <=方向にCreate
    createtempTask(currentPos.x, createStart.y, createStart.x - currentPos.x, defaultcolor);
  }

});

const pointerUp = async (e) => {
  // ObjectをDrag中
  if (draggingTask) {
    let tempMoveObj = app.stage.getChildByLabel(tempMoveObjLabel);
    if (tempMoveObj) {
      await createTask(draggingTask.label, tempMoveObj.my_x, tempMoveObj.my_y, tempMoveObj.my_width, tempMoveObj.my_color, '');
      tempMoveObj.destroy({ children: true, texture: true, textureSource: true, context: true });
    }
    draggingTask = false;
    return;
  }
  // ObjectをResize中
  if (resizingTask) {
    let tempMoveObj = app.stage.getChildByLabel(tempMoveObjLabel);
    if (tempMoveObj) {
      if (tempMoveObj.my_width >= cellWidth / 12) {
        await createTask(resizingTask.label, tempMoveObj.my_x, tempMoveObj.my_y, tempMoveObj.my_width, tempMoveObj.my_color, '');
      }
      else {
        await createTask(resizingTask.label, tempMoveObj.my_x, tempMoveObj.my_y, cellWidth / 12, tempMoveObj.my_color, '');
      }
      tempMoveObj.destroy({ children: true, texture: true, textureSource: true, context: true });
    }
    resizingTask = false;
  }

  if (createStart.x == -1) return;
  // ObjectをCreate中
  let tempObj = app.stage.getChildByLabel(tempObjLabel);
  if (tempObj) {
    if (tempObj.my_width >= cellWidth / 12) {
      await createTask(crypto.randomUUID().toString(), tempObj.my_x, tempObj.my_y, tempObj.my_width, defaultcolor, '');
    }
    tempObj.destroy({ children: true, texture: true, textureSource: true, context: true });
  }
  createStart = { x: -1, y: -1 };
}

app.stage.on('pointerup', pointerUp);
app.stage.on('pointerupoutside', pointerUp);

画面上部のtaskchangeのボタン押下のイベントで

対象Taskを一度削除しておき、指定されたInput内の値で新たにTaskを作成する。

Canvas要素でカレンダーアプリを作ってみる3_4.gif

main.js
taskchange.addEventListener('click', async (e) => {
  const taskId = e.target.dataset.taskid;
  if (taskId == '') return;  
  const color = taskcolor.value;
  const title = tasktitle.value;
  let targetcontainer = app.stage.getChildByLabel(taskId);
  if (targetcontainer) {
    blurTask();
    targetcontainer.destroy({ children: true, texture: true, textureSource: true, context: true });
    // re create
    await createTask(taskId, targetcontainer.my_x, targetcontainer.my_y, targetcontainer.my_width, color, title);
    focusTask(taskId);
  }
});

画面上部のtaskdeleteのボタン押下のイベントでtaskを削除する。

Canvas要素でカレンダーアプリを作ってみる3_5.gif

main.js
taskdelete.addEventListener('click', async (e) => {
  const taskId = e.target.dataset.taskid;
  if (taskId == '') return;
  let targetcontainer = app.stage.getChildByLabel(taskId);
  if (targetcontainer) {
    deletedistinctTask(taskId);
    targetcontainer.destroy({ children: true, texture: true, textureSource: true, context: true });
    // focus destroy
    blurTask();
  }
});

画面上部のcalendarsaveのボタン押下のイベントでtaskを保存する。

画面上部の左右のボタン押下のイベントでtaskを保存し、カレンダーを翌月OR先月にする。

Canvas要素でカレンダーアプリを作ってみる3_6.gif

main.js
/**
 * Taskを保存する関数
 */
const saveTasks = async () => {
  let count = 0;
  const fmtYYYYMM = calendar_month.innerText.replaceAll('-', '');
  for (const child of app.stage.children) {
    if (child.my_color && child.my_title && child.my_width && child.my_x && child.my_y) {
      const ret = await db.query(`INSERT INTO task_master (taskid,title,color,x,y,width,yearmonth) 
        SELECT $1,$2,$3,$4,$5,$6,$7
        WHERE NOT EXISTS ( SELECT 1 FROM task_master WHERE taskid = $1 );`,
        [child.label, child.my_title, child.my_color, child.my_x, child.my_y, child.my_width, Number(fmtYYYYMM)]);
      count = count + ret.affectedRows;
    }
  }
  if (count > 0) {
    window.alert(`${calendar_month.innerText}のTaskを${count}件保存しました`)
  }
}

calendarsave.addEventListener('click', async (e) => {
  await saveTasks();
});

document.getElementById('arrowleft').addEventListener('click', async () => {
  await saveTasks();
  const prevdt = addMonth(parse({ date: calendar_month.innerText + '-01', format: 'YYYY-MM-DD', locale: 'ja', }), -1);
  maxDays = monthDays(prevdt);
  gridHeight = cellHeight * maxDays;
  calendar_month.innerText = format({ date: prevdt, format: 'YYYY-MM', tz: 'Asia/Tokyo', });

  blurTask();
  const fmtYYYYMM = calendar_month.innerText.replaceAll('-', '');
  while (app.stage.children.length > 0) {
    app.stage.children.shift().destroy({ children: true, texture: true, textureSource: true, context: true });
  }
  await calenadarView(fmtYYYYMM);
})

document.getElementById('arrowright').addEventListener('click', async () => {
  await saveTasks();
  const nextdt = addMonth(parse({ date: calendar_month.innerText + '-01', format: 'YYYY-MM-DD', locale: 'ja', }), 1);
  maxDays = monthDays(nextdt);
  gridHeight = cellHeight * maxDays;
  calendar_month.innerText = format({ date: nextdt, format: 'YYYY-MM', tz: 'Asia/Tokyo', });

  blurTask();
  const fmtYYYYMM = calendar_month.innerText.replaceAll('-', '');
  while (app.stage.children.length > 0) {
    app.stage.children.shift().destroy({ children: true, texture: true, textureSource: true, context: true });
  }
  await calenadarView(fmtYYYYMM);
})

まだまだ、微妙なところはありますが、完成!💪

今回の成果物

ソース

DemoURL

まとめ

全3記事でカレンダーアプリをPixiJSで実装してみた。

思いのほか、PGliteの処理が重たいので、時間があればSuspend処理や

シンプルにローカルストレージを使ったほうが見栄えはいいと思います。🤔

PixiJSは2Dに特化していて、学習コストも低かったので引き続き、学習していきたい。📝

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?