2019-10-04に投稿

Vue + Buefy で画像ファイルのプレビューを実装しようとしたらonChangeが効かなくて少しだけ困った話

まあ、自分のやろうとしたことが、正攻法じゃなかっただけかもしれないけど。

動作環境

まずは、一応
OS: macOS High Sierra 10.13.6
ブラウザ: Chrome 77.0.3865.90
vue: 2.6.10
buefy: 0.8.4

はじめに

vue + buefy でwebサイトを作っていて、画像ファイルのアップロードとプレビューを実装しようと思った。
それ自体は調べれば色々出てくるので難しいものではなかった。(ちょっと疑問は残ったけど1

だいぶ雑に書くけど

<template>
    <div>
        <input type="file" @change="changeImage"/></br>
        <img :src="image" />
    </div>
</template>

<script>
export default {
    data:function(){
        return {
            image:"",
        }
    },
    methods:{
        changeImage:function(e){
            const file = e.target.files[0]
            if(file){
                this.image = window.URL.createObjectURL(file)
            }
            else{
                this.image = ""
            }
        }
    }
}
</script>

こんな感じでできる。
(ほんとは、ファイルがないときはimgタグ隠すとか、fileは画像ファイルだけ受け付けるとか色々必要だけど)
input_選択前.png
input_選択後.png

ところがこれを

<template>
    <div>
        <b-input type="file" @change="changeImage"></b-input></br>
        <img :src="image" />
    </div>
</template>

<script>
export default {
    data:function(){
        return {
            image:"",
        }
    },
    methods:{
        changeImage:function(e){
            const file = e.target.files[0]
            if(file){
                this.image = window.URL.createObjectURL(file)
            }
            else{
                this.image = ""
            }
        }
    }
}
</script>

buefyのb-inputに変更したら途端に動かなくなった。
どうもonChangeが発火していないみたい。

b-input_選択前.png
b-input_選択後.png

解決策

原因がはっきりしないのだけど、(というか調べていない)とりあえずなんとかする方法はある。

1. b-inputをやめる

そもそも、元々動いていたのだから素直にinputを使う。で、bulmaなり素のcssでデザインをなんとかすれば良い。

2. b-inputではなくb-uploadを使う

こっちの方が正攻法のような気がする。
そもそもbuefyにはb-input以外に、アップロード用のコンポーネントb-uploadがある。
(正体は input type="file" っぽいけど)
こっちだとv-modelでfileオブジェクトをバインドできるので、onChangeイベントも要らず算出プロパティでできたりする。
※ちなみにinputでv-modelを使おうとすると、type="file"の場合、vueのエラーになる。b-uploadも最終的にはinputになるのに、何故……?

<template>
    <div>
        <b-field class="file">
            <b-upload v-model="file">
                <a class="button is-primary">
                    <b-icon icon="upload"></b-icon>
                    <span>Click to upload</span>
                </a>
            </b-upload>
            <span class="file-name" v-if="file">
                {{ file.name }}
            </span>
        </b-field>
        <img :src="image" />
    </div>
</template>

<script>
export default {
    data:function(){
        return {
            file:null
        }
    },
    computed:{
        image:function(){
            return this.file ? window.URL.createObjectURL(this.file) : ""
        }
    }
}
</script>

b-upload_選択前.png
(雑にやったせいかアイコンが出てないけど……)
b-upload_選択後.png

番外編 b-inputでv-modelを使う(ダメでした)

この記事を書いていて、「あれ? 最終的にinputになるb-uploadがv-model使えるのなら、b-inputのtype="file"でもv-model使えるんじゃね?」って思ってやってみたけどダメだった。
vue側ではエラーにならないけど、バインドされるのがFileオブジェクトではなくファイルパスの文字列だった。
b-uploadはコンポーネントの中で色々よしなに頑張ってくれているんだろうなあ。

終わりに

個人的には、いかにも「ファイルアップロードです!」みたいな見た目が嫌いではないし、対応としてはどっちでも良いと思うのだけれど、まあ、状況に合わせて対応すれば良いと思う。


  1. みんなonChangeでFileオブジェクト取得してFileReaderでonloadしてreadAsDataURLでimgのsrcに入れてるんだけど、今回やってるみたいにFileオブジェクトからwindow.URL.createObjectURLじゃダメなの? ↩︎


hammhiko

恥を晒して生きていきます。

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント