ドラッグ&ドロップで .glb ファイルをアップロードし、サーバーに保存してから A-Frame で表示する

ドラッグ&ドロップで .glb ファイルをアップロードし、サーバーに保存してから A-Frame で表示する構成 を作成します。


目次

✅ 完成イメージ

  1. ユーザーが .glb ファイルをドラッグ&ドロップ
  2. JavaScript がファイルを PHP にアップロード
  3. アップロード完了後、自動的に view.php に遷移して表示

📁 ディレクトリ構成(例)

/your-project/
├── index.html       ← ドラッグ&ドロップ画面
├── upload.php       ← アップロード処理(PHP)
├── view.php         ← A-Frame で表示するページ
└── uploads/         ← アップロード先フォルダ

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>GLBドラッグ&ドロップ</title>
  <style>
    #drop-area {
      border: 2px dashed #aaa;
      border-radius: 10px;
      width: 100%;
      max-width: 400px;
      margin: 50px auto;
      padding: 30px;
      text-align: center;
      font-family: sans-serif;
      color: #555;
    }
    #drop-area.hover {
      border-color: #333;
      background-color: #f0f0f0;
    }
  </style>
</head>
<body>

<div id="drop-area">
  <p>ここに .glb ファイルを<br>ドラッグ&ドロップ</p>
  <div id="file-list"></div>
</div>

<script>
const dropArea = document.getElementById('drop-area');
const fileList = document.getElementById('file-list');

// イベント処理
['dragenter', 'dragover'].forEach(eventName => {
  dropArea.addEventListener(eventName, (e) => {
    e.preventDefault();
    e.stopPropagation();
    dropArea.classList.add('hover');
  }, false);
});

['dragleave', 'drop'].forEach(eventName => {
  dropArea.addEventListener(eventName, (e) => {
    e.preventDefault();
    e.stopPropagation();
    dropArea.classList.remove('hover');
  }, false);
});

dropArea.addEventListener('drop', (e) => {
  const file = e.dataTransfer.files[0];
  if (file && file.name.endsWith('.glb')) {
    uploadFile(file);
  } else {
    fileList.innerHTML = '<p style="color:red;">.glbファイルのみアップロード可能です。</p>';
  }
});

function uploadFile(file) {
  const formData = new FormData();
  formData.append('glbfile', file);

  fetch('upload.php', {
    method: 'POST',
    body: formData
  })
  .then(res => res.text())
  .then(response => {
    if (response.startsWith('OK:')) {
      const filename = response.replace('OK:', '').trim();
      window.location.href = `view.php?file=${encodeURIComponent(filename)}`;
    } else {
      fileList.innerHTML = `<p style="color:red;">${response}</p>`;
    }
  })
  .catch(() => {
    fileList.innerHTML = '<p style="color:red;">アップロードエラーが発生しました。</p>';
  });
}
</script>

</body>
</html>

upload.php

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['glbfile'])) {
    $uploadDir = 'uploads/';
    $file = $_FILES['glbfile'];
    $filename = basename($file['name']);

    // セキュリティチェック
    if (!preg_match('/\.glb$/i', $filename)) {
        echo "エラー: .glbファイルのみ許可されています。";
        exit;
    }

    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0777, true);
    }

    $targetFile = $uploadDir . uniqid() . '_' . $filename;

    if (move_uploaded_file($file['tmp_name'], $targetFile)) {
        echo "OK:" . basename($targetFile);
    } else {
        echo "ファイルの保存に失敗しました。";
    }
} else {
    echo "不正なリクエストです。";
}
?>

view.php

<?php
$filename = isset($_GET['file']) ? $_GET['file'] : '';
$filepath = 'uploads/' . $filename;

if (!preg_match('/^[\w\-\.]+\.glb$/', $filename) || !file_exists($filepath)) {
    die('ファイルが見つかりません。');
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title><?= htmlspecialchars($filename) ?> を表示</title>
  <script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
</head>
<body>
  <h2>アップロードされたGLBを表示</h2>
  <a-scene background="color: #ECECEC">
    <a-assets>
      <a-asset-item id="model" src="<?= htmlspecialchars($filepath) ?>"></a-asset-item>
    </a-assets>
    <a-entity gltf-model="#model" position="0 1.6 -2" rotation="0 45 0"></a-entity>
    <a-light type="ambient" color="#fff"></a-light>
    <a-light type="directional" position="1 2 1" intensity="0.5"></a-light>
    <a-camera position="0 1.6 0"></a-camera>
  </a-scene>
  <p><a href="index.html">戻る</a></p>
</body>
</html>

✅ ポイント

  • .glb のみ許可
  • ファイル名をユニークにすることで重複回避
  • サーバー上に保存されたファイルを A-Frame で読み込み

もっと進化させてみたい場合:

  • サムネイル表示
  • 複数ファイルアップロード
  • ファイル削除機能
  • DBと連携

なども可能です。必要なら一緒に作れます!

よかったらシェアしてね!
目次