絵文字で絵を描く「Emoji Map」


画像

・ダウンロードされる方はこちら。↓

・ソースコードはこちら。↓

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Emoji Pixel Studio</title>
<style>
  :root {
    --bg-app: #e0e0e0;
    --bg-panel: #f5f5f5;
    --border: #ccc;
    --accent: #4a90e2;
    --text: #333;
  }
  body {
    margin: 0;
    padding: 0;
    font-family: sans-serif;
    background: var(--bg-app);
    height: 100vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    user-select: none; /* UIの文字選択を防ぐ */
  }

  /* メニューバー */
  #menubar {
    background: #333;
    color: white;
    padding: 5px 10px;
    display: flex;
    gap: 15px;
    font-size: 14px;
  }
  .menu-item { cursor: pointer; padding: 2px 8px; }
  .menu-item:hover { background: #555; }

  /* ツールバー */
  #toolbar {
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
    padding: 8px;
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
  }
  .tool-btn {
    width: 36px;
    height: 36px;
    border: 1px solid #999;
    background: white;
    border-radius: 4px;
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 20px;
    position: relative;
  }
  .tool-btn:hover { background: #eee; }
  .tool-btn.active {
    background: #d0e8ff;
    border-color: var(--accent);
    box-shadow: inset 0 0 3px rgba(0,0,0,0.2);
  }
  .separator { width: 1px; height: 30px; background: #ccc; margin: 0 5px; }

  /* メインエリア */
  #main-area {
    display: flex;
    flex: 1;
    overflow: hidden;
  }

  /* 左パネル(パレット) */
  #left-panel {
    width: 280px;
    background: var(--bg-panel);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    z-index: 5;
  }
  #category-header {
    padding: 5px;
    display: flex;
    gap: 5px;
    background: #ddd;
    border-bottom: 1px solid #ccc;
  }
  #category-tabs {
    flex: 1;
    overflow-x: auto;
    display: flex;
    gap: 2px;
    scrollbar-width: thin;
  }
  .cat-tab {
    padding: 4px 8px;
    background: #eee;
    border: 1px solid #ccc;
    font-size: 12px;
    cursor: pointer;
    white-space: nowrap;
  }
  .cat-tab.active { background: white; border-bottom-color: white; font-weight: bold; }
  
  #palette-grid {
    flex: 1;
    padding: 10px;
    overflow-y: auto;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
    gap: 4px;
    align-content: start;
  }
  .emoji-cell {
    font-size: 24px;
    text-align: center;
    cursor: pointer;
    border-radius: 4px;
    padding: 2px;
  }
  .emoji-cell:hover { background: #ddd; }
  .emoji-cell.selected { background: #badfff; outline: 2px solid var(--accent); }

  /* キャンバスエリア */
  #canvas-wrapper {
    flex: 1;
    position: relative;
    overflow: hidden; /* スクロールはCanvas内で行うか、親のoverflowでやるか */
    background: #888;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  /* スクロールバー付きコンテナ */
  #scroll-container {
    width: 100%;
    height: 100%;
    overflow: auto;
    display: flex;
    justify-content: center;
    align-items: center;
    background-image: 
      linear-gradient(45deg, #ccc 25%, transparent 25%), 
      linear-gradient(-45deg, #ccc 25%, transparent 25%), 
      linear-gradient(45deg, transparent 75%, #ccc 75%), 
      linear-gradient(-45deg, transparent 75%, #ccc 75%);
    background-size: 20px 20px;
    background-color: #fff;
  }
  canvas {
    background: white;
    box-shadow: 0 0 10px rgba(0,0,0,0.3);
    cursor: crosshair;
  }

  /* ポップアップメニュー */
  .popup-menu {
    position: absolute;
    background: white;
    border: 1px solid #999;
    box-shadow: 2px 2px 10px rgba(0,0,0,0.2);
    min-width: 150px;
    z-index: 1000;
    display: none;
    padding: 5px 0;
  }
  .popup-item {
    padding: 5px 15px;
    cursor: pointer;
    font-size: 14px;
  }
  .popup-item:hover { background: #eee; }
  .popup-sep { height: 1px; background: #ddd; margin: 3px 0; }

  /* ダイアログオーバーレイ */
  #overlay {
    position: fixed; top:0; left:0; right:0; bottom:0;
    background: rgba(0,0,0,0.4);
    display: none;
    z-index: 2000;
    justify-content: center;
    align-items: center;
  }
  .dialog {
    background: white;
    padding: 20px;
    border-radius: 5px;
    width: 400px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    box-shadow: 0 5px 15px rgba(0,0,0,0.3);
  }
  textarea { height: 150px; resize: vertical; font-family: monospace; }
</style>
</head>
<body>

<div id="menubar">
  <div class="menu-item" onclick="fileMenu('new')">新規作成</div>
  <div class="menu-item" onclick="fileMenu('open')">開く</div>
  <div class="menu-item" onclick="fileMenu('save')">上書き保存</div>
  <div class="menu-item" onclick="fileMenu('saveAs')">別名で保存(txt)</div>
  <div class="menu-item" onclick="editMenu('copy')">コピー</div>
  <div class="menu-item" onclick="editMenu('paste')">貼り付け</div>
  <div class="menu-item" onclick="toggleGrid()">グリッド表示切替</div>
</div>

<div id="toolbar">
  <button class="tool-btn active" id="btn-pen" title="ペン (Left Click)" onclick="setTool('pen')">✏️</button>
  <button class="tool-btn" id="btn-eraser" title="消しゴム" onclick="setTool('eraser')">🧹</button>
  <div class="separator"></div>
  <button class="tool-btn" id="btn-select" title="範囲選択 (ドラッグで移動)" onclick="setTool('select')">⛶</button>
  <button class="tool-btn" id="btn-fill" title="塗りつぶし" onclick="setTool('fill')">🪣</button>
  <button class="tool-btn" id="btn-dropper" title="スポイト (Right Click)" onclick="setTool('dropper')">💉</button>
  <div class="separator"></div>
  <button class="tool-btn" id="btn-rect" title="四角形" onclick="setTool('rect')">⬜</button>
  <button class="tool-btn" id="btn-rect-fill" title="四角形(塗り)" onclick="setTool('rect-fill')">⬛</button>
  <button class="tool-btn" id="btn-circle" title="円" onclick="setTool('circle')">◯</button>
  <button class="tool-btn" id="btn-circle-fill" title="円(塗り)" onclick="setTool('circle-fill')">●</button>
  <div class="separator"></div>
  <button class="tool-btn" title="拡大" onclick="changeZoom(0.2)">➕</button>
  <button class="tool-btn" title="縮小" onclick="changeZoom(-0.2)">➖</button>
  <span style="font-size:12px; margin-left:5px;">現在: <span id="current-emoji-display">⬛</span></span>
</div>

<div id="main-area">
  <div id="left-panel">
    <div id="category-header">
      <div id="category-tabs"></div>
      <button onclick="addCategory()" style="font-size:10px;">+</button>
      <button onclick="deleteCategory()" style="font-size:10px;">🗑️</button>
    </div>
    <div id="palette-grid"></div>
  </div>

  <div id="canvas-wrapper">
    <div id="scroll-container">
      <canvas id="editor-canvas"></canvas>
    </div>
  </div>
</div>

<div id="ctx-menu" class="popup-menu">
  <div class="popup-item" onclick="setTool('dropper'); hideCtx();">スポイト</div>
  <div class="popup-sep"></div>
  <div class="popup-item" onclick="editMenu('cut'); hideCtx();">カット</div>
  <div class="popup-item" onclick="editMenu('copy'); hideCtx();">コピー</div>
  <div class="popup-item" onclick="editMenu('paste'); hideCtx();">貼り付け</div>
  <div class="popup-sep"></div>
  <div class="popup-item" onclick="editMenu('selectAll'); hideCtx();">すべて選択</div>
  <div class="popup-item" onclick="editMenu('deselect'); hideCtx();">選択解除</div>
</div>

<div id="overlay">
  <div class="dialog">
    <h3 id="dialog-title">Title</h3>
    <textarea id="dialog-text"></textarea>
    <div style="text-align:right;">
      <button onclick="closeDialog()">閉じる</button>
    </div>
  </div>
</div>

<script>
/**
 * 定数・設定
 */
const SPACE = ' '; // 全角スペース
const INIT_SIZE = 20;
let CELL_SIZE = 32;

// 状態管理
let grid = []; // 2D配列
let width = INIT_SIZE;
let height = INIT_SIZE;
let zoom = 1.0;
let currentTool = 'pen';
let currentEmoji = '⬛';
let showGrid = true;

// 選択範囲状態
let selection = null; // {x, y, w, h, floatingBuffer: null}
let isDraggingSelection = false;
let dragStartPos = null;

// 履歴(Undoは簡易実装のため今回は省略、構造のみ)
let history = [];

// カテゴリデータ
let categories = {
  "基本": ["⬛","⬜","🟥","🟦","🟧","🟨","🟩","🟪","🟫"],
  "顔": ["😀","😂","😊","🥰","😎","🤔","😭","😡"],
  "自然": ["🌲","🌷","🌻","🔥","💧","⭐","🌙"],
  "記号": ["❤","💔","💯","💢","💤","💨","💥"]
};
let currentCat = "基本";

/**
 * 初期化
 */
const canvas = document.getElementById('editor-canvas');
const ctx = canvas.getContext('2d');
const scrollContainer = document.getElementById('scroll-container');

window.onload = () => {
  initGridData(width, height);
  renderCategoryTabs();
  renderPalette();
  updateCanvasSize();
  draw();
  
  // マウスイベント設定
  canvas.addEventListener('mousedown', handleMouseDown);
  window.addEventListener('mousemove', handleMouseMove);
  window.addEventListener('mouseup', handleMouseUp);
  
  // 右クリック
  canvas.addEventListener('contextmenu', handleRightClick);
  document.addEventListener('click', hideCtx); // 他をクリックしたら閉じる
};

/**
 * データ操作系
 */
function initGridData(w, h) {
  grid = [];
  for(let y=0; y<h; y++) {
    let row = [];
    for(let x=0; x<w; x++) {
      row.push(SPACE);
    }
    grid.push(row);
  }
}

/**
 * 描画システム (Canvas)
 */
function updateCanvasSize() {
  canvas.width = width * CELL_SIZE * zoom;
  canvas.height = height * CELL_SIZE * zoom;
  draw();
}

function draw() {
  // 背景クリア
  ctx.fillStyle = '#fff';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  ctx.save();
  ctx.scale(zoom, zoom);

  ctx.font = `${Math.floor(CELL_SIZE * 0.8)}px sans-serif`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';

  // グリッドと文字の描画
  for(let y=0; y<height; y++) {
    for(let x=0; x<width; x++) {
      // 選択範囲の移動中は、元の場所を空白として描画(移動中の絵は後で描く)
      if (selection && selection.floatingBuffer && isInSelection(x, y) && isDraggingSelection) {
        drawCell(x, y, SPACE, true); 
      } else {
        drawCell(x, y, grid[y][x]);
      }
    }
  }

  // 選択範囲の枠とフローティングコンテンツ
  if (selection) {
    // 枠線
    ctx.strokeStyle = '#007bff';
    ctx.lineWidth = 2;
    ctx.setLineDash([4, 2]);
    ctx.strokeRect(selection.x * CELL_SIZE, selection.y * CELL_SIZE, selection.w * CELL_SIZE, selection.h * CELL_SIZE);
    ctx.setLineDash([]);

    // 移動中の絵を描画
    if (selection.floatingBuffer) {
      for(let ly=0; ly<selection.h; ly++) {
        for(let lx=0; lx<selection.w; lx++) {
          const char = selection.floatingBuffer[ly][lx];
          if(char !== null) { // 透明部分は描かない
            const targetX = selection.x + lx;
            const targetY = selection.y + ly;
            drawCell(targetX, targetY, char, false); // 上書き描画
          }
        }
      }
    }
  }

  // グリッド線
  if (showGrid) {
    ctx.strokeStyle = '#e0e0e0';
    ctx.lineWidth = 1;
    ctx.beginPath();
    for(let x=0; x<=width; x++) {
      ctx.moveTo(x*CELL_SIZE, 0);
      ctx.lineTo(x*CELL_SIZE, height*CELL_SIZE);
    }
    for(let y=0; y<=height; y++) {
      ctx.moveTo(0, y*CELL_SIZE);
      ctx.lineTo(width*CELL_SIZE, y*CELL_SIZE);
    }
    ctx.stroke();
  }

  ctx.restore();
}

function drawCell(x, y, char, isBgOnly=false) {
  const px = x * CELL_SIZE;
  const py = y * CELL_SIZE;
  
  // 背景(空白の場合はわかりやすくグレーにしてもいいが、今回は白)
  /*
  if(char === SPACE) {
    ctx.fillStyle = '#fafafa';
    ctx.fillRect(px, py, CELL_SIZE, CELL_SIZE);
  }
  */

  if (!isBgOnly && char !== SPACE) {
    ctx.fillStyle = '#000';
    ctx.fillText(char, px + CELL_SIZE/2, py + CELL_SIZE/2 + 2);
  }
}

/**
 * ツールロジック
 */
let isMouseDown = false;
let startX, startY; // ドラッグ開始用
let previewShape = null; // シェイプ描画中のプレビュー(未実装だが拡張用)

function handleMouseDown(e) {
  if(e.button !== 0) return; // 左クリックのみ
  const pos = getGridPos(e);
  if(!pos) return;
  
  isMouseDown = true;
  startX = pos.x;
  startY = pos.y;

  // 選択範囲内のクリック -> 移動モード開始
  if (currentTool === 'select' && selection && isInSelection(pos.x, pos.y)) {
    isDraggingSelection = true;
    dragStartPos = {x: pos.x, y: pos.y, selX: selection.x, selY: selection.y};
    
    // まだフローティング化していなければ、グリッドからデータを切り取って浮かす
    if (!selection.floatingBuffer) {
      floatSelection();
    }
    return;
  }

  // 選択ツールだが範囲外 -> 新規選択開始
  if (currentTool === 'select') {
    commitSelection(); // 以前の選択を確定
    selection = {x: pos.x, y: pos.y, w: 1, h: 1, floatingBuffer: null};
    draw();
    return;
  }

  // 通常描画ツール
  commitSelection(); // 選択解除
  useTool(pos.x, pos.y);
  draw();
}

function handleMouseMove(e) {
  if(!isMouseDown) return;
  const pos = getGridPos(e);
  if(!pos) return;

  // 選択範囲のドラッグ移動
  if (isDraggingSelection && selection) {
    const dx = pos.x - dragStartPos.x;
    const dy = pos.y - dragStartPos.y;
    selection.x = dragStartPos.selX + dx;
    selection.y = dragStartPos.selY + dy;
    draw();
    return;
  }

  // 範囲選択の拡大
  if (currentTool === 'select' && !isDraggingSelection) {
    const minX = Math.min(startX, pos.x);
    const minY = Math.min(startY, pos.y);
    const w = Math.abs(pos.x - startX) + 1;
    const h = Math.abs(pos.y - startY) + 1;
    selection = {x: minX, y: minY, w: w, h: h, floatingBuffer: null};
    draw();
    return;
  }

  // ペン・消しゴム(ドラッグ描画)
  if (['pen', 'eraser'].includes(currentTool)) {
    useTool(pos.x, pos.y);
    draw();
  }
}

function handleMouseUp(e) {
  if (!isMouseDown) return;
  isMouseDown = false;
  isDraggingSelection = false;

  const pos = getGridPos(e);
  if(!pos) return; // 画面外リリース

  // 図形ツールの確定
  if (['rect', 'rect-fill', 'circle', 'circle-fill'].includes(currentTool)) {
    drawShape(startX, startY, pos.x, pos.y, currentTool);
    draw();
  }
}

function handleRightClick(e) {
  e.preventDefault();
  // 右クリックでスポイト(即時実行)
  const pos = getGridPos(e);
  if (pos) {
    const char = grid[pos.y][pos.x];
    if (char !== SPACE) {
      currentEmoji = char;
      document.getElementById('current-emoji-display').innerText = currentEmoji;
      setTool('pen'); // 自動でペンに戻る
    }
  }
  
  // ポップアップメニュー表示
  const menu = document.getElementById('ctx-menu');
  menu.style.display = 'block';
  menu.style.left = e.pageX + 'px';
  menu.style.top = e.pageY + 'px';
}

function getGridPos(e) {
  const rect = canvas.getBoundingClientRect();
  const x = Math.floor((e.clientX - rect.left) / (CELL_SIZE * zoom));
  const y = Math.floor((e.clientY - rect.top) / (CELL_SIZE * zoom));
  
  // 範囲外チェック(選択移動中は外に出ても良いので緩めるか、clampするか)
  // ここではシンプルにGrid内に収める
  if(x < 0 || x >= width || y < 0 || y >= height) {
    // ツールによっては外に出ると都合が悪い
    if(isDraggingSelection) return {x, y}; // 移動中は外も許可(計算用)
    return null;
  }
  return {x, y};
}

/**
 * ツール機能実装
 */
function useTool(x, y) {
  if (x < 0 || x >= width || y < 0 || y >= height) return;

  if (currentTool === 'pen') {
    grid[y][x] = currentEmoji;
  } else if (currentTool === 'eraser') {
    grid[y][x] = SPACE;
  } else if (currentTool === 'fill') {
    floodFill(x, y, grid[y][x], currentEmoji);
  }
}

// 塗りつぶし (再帰またはスタック)
function floodFill(x, y, targetColor, replaceColor) {
  if (targetColor === replaceColor) return;
  if (grid[y][x] !== targetColor) return;

  let stack = [[x, y]];
  while (stack.length) {
    let [cx, cy] = stack.pop();
    if (cx < 0 || cx >= width || cy < 0 || cy >= height) continue;
    if (grid[cy][cx] === targetColor) {
      grid[cy][cx] = replaceColor;
      stack.push([cx + 1, cy]);
      stack.push([cx - 1, cy]);
      stack.push([cx, cy + 1]);
      stack.push([cx, cy - 1]);
    }
  }
}

// 図形描画
function drawShape(x1, y1, x2, y2, type) {
  const minX = Math.min(x1, x2);
  const maxX = Math.max(x1, x2);
  const minY = Math.min(y1, y2);
  const maxY = Math.max(y1, y2);

  for (let y = minY; y <= maxY; y++) {
    for (let x = minX; x <= maxX; x++) {
      let draw = false;
      if (type.includes('rect')) {
        if (type === 'rect-fill') draw = true;
        else draw = (x === minX || x === maxX || y === minY || y === maxY);
      } else if (type.includes('circle')) {
        // 簡易楕円判定
        let a = (maxX - minX) / 2;
        let b = (maxY - minY) / 2;
        let cx = minX + a;
        let cy = minY + b;
        let val = Math.pow((x - cx)/a, 2) + Math.pow((y - cy)/b, 2);
        
        if (type === 'circle-fill') draw = (val <= 1.2); // 少し緩めに
        else draw = (val >= 0.8 && val <= 1.2);
      }

      if (draw) {
        grid[y][x] = currentEmoji;
      }
    }
  }
}

/**
 * 選択範囲・移動ロジック
 */
function isInSelection(x, y) {
  if (!selection) return false;
  return x >= selection.x && x < selection.x + selection.w &&
         y >= selection.y && y < selection.y + selection.h;
}

// 選択範囲の中身を「浮遊バッファ」に移す(Cut & Float)
function floatSelection() {
  if (!selection) return;
  const buffer = [];
  for(let ly=0; ly<selection.h; ly++) {
    let row = [];
    for(let lx=0; lx<selection.w; lx++) {
      const gx = selection.x + lx;
      const gy = selection.y + ly;
      if (gx >= 0 && gx < width && gy >= 0 && gy < height) {
        row.push(grid[gy][gx]);
        grid[gy][gx] = SPACE; // 元の場所は消す
      } else {
        row.push(SPACE);
      }
    }
    buffer.push(row);
  }
  selection.floatingBuffer = buffer;
}

// 浮いている選択範囲をグリッドに焼き付ける(Paste / Drop)
function commitSelection() {
  if (!selection || !selection.floatingBuffer) {
    selection = null;
    return;
  }
  
  // バッファの内容を現在の位置に書き込む
  for(let ly=0; ly<selection.h; ly++) {
    for(let lx=0; lx<selection.w; lx++) {
      const gx = selection.x + lx;
      const gy = selection.y + ly;
      const char = selection.floatingBuffer[ly][lx];
      
      // グリッド範囲内のみ書き込み
      if (gx >= 0 && gx < width && gy >= 0 && gy < height && char !== null) {
        // 全角スペース(透明扱い)の場合は書き込まないほうが自然なツールもあるが、
        // ここでは「移動」なので上書きする
        grid[gy][gx] = char;
      }
    }
  }
  selection = null;
  draw();
}

/**
 * UI・メニュー操作
 */
function setTool(name) {
  // 選択ツール以外に切り替えたら確定する
  if(name !== 'select') commitSelection();
  
  currentTool = name;
  document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active'));
  let btnId = 'btn-' + (['rect-fill','circle-fill'].includes(name) ? name : name.split('-')[0]); 
  if(name === 'pen' || name === 'eraser' || name === 'fill' || name === 'dropper' || name === 'select') {
    document.getElementById('btn-' + name).classList.add('active');
  } else {
    // 図形系
    document.getElementById(btnId).classList.add('active');
  }
}

function changeZoom(delta) {
  zoom = Math.max(0.2, Math.min(3.0, zoom + delta));
  updateCanvasSize();
}

function toggleGrid() {
  showGrid = !showGrid;
  draw();
}

function hideCtx() {
  document.getElementById('ctx-menu').style.display = 'none';
}

/**
 * ファイル・編集メニュー
 */
function fileMenu(action) {
  commitSelection();
  if (action === 'new') {
    if(confirm('現在の内容は消去されます。よろしいですか?')) {
      initGridData(width, height);
      draw();
    }
  } else if (action === 'save') {
    // ローカルストレージに保存(簡易)
    localStorage.setItem('emojiPixelData', JSON.stringify(grid));
    alert('ブラウザに一時保存しました');
  } else if (action === 'open') {
    const data = localStorage.getItem('emojiPixelData');
    if(data) {
      grid = JSON.parse(data);
      draw();
    } else {
      alert('保存されたデータがありません');
    }
  } else if (action === 'saveAs') {
    // テキスト書き出し
    let text = grid.map(row => row.join("")).join("\n");
    showDialog("テキストとしてコピー", text);
  }
}

function editMenu(action) {
  if (action === 'selectAll') {
    commitSelection();
    selection = {x:0, y:0, w:width, h:height, floatingBuffer: null};
    currentTool = 'select';
    setTool('select');
    draw();
  } else if (action === 'deselect') {
    commitSelection();
    draw();
  } else if (action === 'copy') {
    // 簡易実装:選択範囲のテキストをクリップボードへ
    if(!selection) return alert('範囲を選択してください');
    // まだFloatしていない場合はグリッドから読む
    let text = "";
    const buf = selection.floatingBuffer;
    for(let ly=0; ly<selection.h; ly++) {
      for(let lx=0; lx<selection.w; lx++) {
        if(buf) text += buf[ly][lx];
        else text += grid[selection.y+ly][selection.x+lx];
      }
      text += "\n";
    }
    navigator.clipboard.writeText(text).then(()=>alert('コピーしました'));
  }
}

/**
 * パレット管理
 */
function renderCategoryTabs() {
  const container = document.getElementById('category-tabs');
  container.innerHTML = '';
  for(let cat in categories) {
    const div = document.createElement('div');
    div.className = `cat-tab ${cat === currentCat ? 'active' : ''}`;
    div.innerText = cat;
    div.onclick = () => { currentCat = cat; renderCategoryTabs(); renderPalette(); };
    container.appendChild(div);
  }
}

function renderPalette() {
  const container = document.getElementById('palette-grid');
  container.innerHTML = '';
  categories[currentCat].forEach(emoji => {
    const div = document.createElement('div');
    div.className = `emoji-cell ${emoji === currentEmoji ? 'selected' : ''}`;
    div.innerText = emoji;
    div.onclick = () => {
      currentEmoji = emoji;
      document.getElementById('current-emoji-display').innerText = emoji;
      setTool('pen');
      renderPalette();
    };
    container.appendChild(div);
  });
}

function addCategory() {
  const name = prompt("新しいカテゴリ名:");
  if(name && !categories[name]) {
    categories[name] = [];
    currentCat = name;
    renderCategoryTabs();
    renderPalette();
  }
}

function deleteCategory() {
  if(confirm(`${currentCat} カテゴリを削除しますか?`)) {
    delete categories[currentCat];
    const keys = Object.keys(categories);
    if(keys.length > 0) currentCat = keys[0];
    renderCategoryTabs();
    renderPalette();
  }
}

// 外部ダイアログ表示
function showDialog(title, text) {
  document.getElementById('dialog-title').innerText = title;
  document.getElementById('dialog-text').value = text;
  document.getElementById('overlay').style.display = 'flex';
}
function closeDialog() {
  document.getElementById('overlay').style.display = 'none';
}

// パレットへの絵文字追加(左クリックで配置機能の拡張として、パレット上で右クリックなどで追加できると良いが、今回は簡易的に)
// ※実際の運用では「パレットに追加」ボタン等が必要
</script>
</body>
</html>

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

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
絵文字で絵を描く「Emoji Map」|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1