📸

Nuxtでネイティブカメラアプリを起動したい!

2023/11/04に公開

自己紹介

こんにちは、駆け出し web エンジニアのです!
現在は SES 企業で働いていて、主にフロントエンドの開発を行っています。

経験的にもまだ 1 年経っていないので、まだまだ勉強中ですが、よろしくお願いいたします mm

はじめに

今回は、Nuxt で画像を「撮影」もしくは「選択」することができて、かつその画像を API に送信する機能を実装してみました!

この機能を使う想定としては、

  • 画像データを外部 API に送りつけてやりたい!
  • 撮影されたデータや選択されたデータを保存したい!

なんて場面で使えるのではないかと思います。

では早速使った技術を軽く見ていきましょう。

使用技術

package.json
  "devDependencies": {
    "@nuxt/devtools": "latest",
    "nuxt": "^3.8.0",
    "sass": "^1.69.5",
    "vue": "^3.3.7",
    "vue-router": "^4.2.5"
  }

上記のバージョンを使って作成しました。

$ node -v
v18.15.0

node のバージョンは上記の通りです。

Vue は composition API で書いています!

実装

今回使用するファイルは

  • app.vue
  • server/api/uploader.ts

の 2 つになります。
この辺は皆さんのプロジェクトの構成に合わせて柔軟に変更していただければと思います。

私の場合は、

$ npx nuxi@latest init shooting-sample

として始めたばっかなので、app.vueにベタ書きです mm

app.vue

では先にクライアント側を用意していきましょう!

app.vue
<template>
  <div class="input-area">
    <label for="upload">写真を撮影・選択する</label>
    <input
      id="upload"
      type="file"
      name="image"
      accept="image/*"
    />
  </div>
</template>

一旦こんな感じで用意しておきます。
accept="image/*"で画像ファイルのみを選択できるようにするために指定しています。

ローカルで確認してみると、
写真を撮影・選択するファイルを選択選択されていません

みたいな感じで表示されると思います。
「写真を撮影・選択する」以降は不要なので、見えなくしたいと思います!

app.vue
<style scoped lang="scss">
.input-area > input {
  display: none;
}
</style>

これで見えなくなったと思います!
display: none;で非表示にしているだけなので、input自体は機能します!

では次に、inputにファイルが選択された時に発火するイベントを設定していきます!

app.vue
<template>
  <div class="input-area">
    <label for="upload">写真を撮影・選択する</label>
    <input
      ref="uploadFile"
      type="file"
      name="image"
      id="upload"
      accept="image/*"
      @change="selectedFile"
    />
  </div>
</template>

ref="uploadFile"@change="selectedFile"を追加しています。
次に関数を用意してあげます!

app.vue
<script setup lang="ts">
const uploadFile = ref<HTMLInputElement | null>(null);

const selectedFile = async () => {
  console.log(uploadFile.value?.files);
};
</script>

これでファイルを追加したときに、コンソールに選択されたファイルの情報が表示されると思います!
実際に操作をしてみてください!

yarn dev --host

とすると、IP アドレス付きの URL で開発サーバーを立ち上げることができるので、同じ Wi-Fi に繋がっているスマホがあれば、スマホからも開発サーバーにアクセスすることができます!

では関数の中身をもっと充実させていきましょう!

app.vue
const selectedFile = async () => {
  const files = uploadFile.value?.files;
  if (!files) {
    console.error('No files selected');
    return;
  }
  const file = files[0];
  const fileData = new FormData();
  fileData.append('image', file);
  const { data: resData } = await useFetch('/api/uploader', {
    method: 'POST',
    body: fileData,
  });
  // NOTE: API実行後の結果確認用
  console.log(resData.value);
};

という感じにしました!
ファイルのデータをFormDataという形式に変換してから、useFetchを使ってserver/api/uploader.tsに対してfileDataを POST しています!

app.vueの方はこれで終わりです!

全体のコードはこちら
app.vue
<script setup lang="ts">
const uploadFile = ref<HTMLInputElement | null>(null);

const selectedFile = async () => {
  const files = uploadFile.value?.files;
  if (!files) {
    console.error('No files selected');
    return;
  }
  const file = files[0];
  const fileData = new FormData();
  fileData.append('image', file);
  const { data: resData } = await useFetch('/api/uploader', {
    method: 'POST',
    body: fileData,
  });
  // NOTE: API実行後の結果確認用
  console.log(resData.value);
};
</script>
<template>
  <div class="input-area">
    <label for="upload">写真を撮影・選択する</label>
    <input
      ref="uploadFile"
      type="file"
      name="image"
      id="upload"
      accept="image/*"
      @change="selectedFile"
    />
  </div>
</template>
<style scoped lang="scss">
.input-area > input {
  display: none;
}
</style>

それでは API 側の処理も書いていきます!

server/api/uploader.ts

server/api/uploader.tsを作成したら、以下のように書いていきます!

server/api/uploader.ts
import { writeFile } from 'fs/promises';

export default defineEventHandler(async event => {
  const files = await readMultipartFormData(event);
  if (!files || !files.length) {
    throw createError({
      statusCode: 400,
      statusMessage: 'image not found',
    });
  } else if (files[0].name === 'image' && files[0].filename) {
    const { filename, data } = files[0];
    const filePath = `public/${filename}`;
    await writeFile(filePath, data);
    return {
      message: 'success',
    };
  } else {
    return {
      message: "It's not image",
    };
  }
});

event にはapp.vueから送られてきたfileDataが入っています。
readMultipartFormDataを使うことで、FormData形式のデータを変換してくれるようです。

filesには、

[
  {
    name: 'image',
    filename: 'ファイル名.png',
    type: 'image/png',
    data: <Buffer ... 29523 more bytes>
  }
]

といったイメージでデータが入っていると思います。
nameapp.vuefileData.append('image', file);としているので、imageとなっています。
filenameは選択されたファイルの名前、
typeは選択されたファイルの種類、
dataは選択されたファイルのデータです。バイナリデータとかですかね、、?

await writeFile(filePath, data);

で、publicディレクトリにファイルを書き込んでいます!
これで、クライアント側から送られてきた画像データを保存することができました!

まとめ

ということで今回は、Nuxt を使ってネイティブのカメラアプリを起動したり、画像を選択してその画像を API に送信する機能を実装してみました!

何かが皆さんのお役に立てば幸いです!

GitHubで編集を提案

Discussion