🌵
Vue.jsで画像を作成しRailsのActive Storageで保存する
Railsアプリケーション内でVue.jsを使って画像を作成するアプリを制作していて色々と詰まった部分があったので記録に残しておこうと思います。
ざっと流れを説明しておくと以下のようになります。
- Vue.js + Konva.jsを使って
canvas
タグで図形を作成 - Base64形式のData URLに変換
- RailsのActive Storageで保存
開発環境
- Rails 6.1.4
- Vue 3.1.5
- Konva 8.1.1(
canvas
タグで図形などを描画するJavaScriptライブラリ)
Konva - JavaScript 2d canvas library
環境構築などの部分は省略します
VueとKonvaで画像を作成しData URLに変換、画像表示させる
今回はChart
モデルの_form.html.erb
内にVueを描画します
views/charts/_form.html.erb
<div id="js-chart"></div>
javascript/chart.js
import { createApp } from 'vue'
import App from './chart.vue'
document.addEventListener('DOMContentLoaded', () => {
const selector = '#js-chart';
if(document.querySelector(selector)){
createApp(App).mount(selector);
}
})
chart.vue
<template>
<div id="chart"></div>
~ 中略 ~
<img id="img">
</template>
公式にサンプルコード付きで様々なパターンの図形の描画方法が載っているので割愛します。
chart.vue
// data関数でstageとimageUrlを定義しておく
data() {
return {
stage: '',
imageUrl: ''
}
}
chart.vue
this.stage = new Konva.Stage({
width: 500,
height: 800,
container: 'chart' // template内の<div id="chart"></div>のid名を指定して紐付ける
});
土台になるstage
インスタンスを生成しこれにレイヤーや図形のインスタンスを追加していくことで1つのcanvas
さまざまな図形を組み合わせて画像を作ることができます。
例)
- stage
- background_layer
- base_line
- main_layer
- circle1
- circle2
- background_layer
chart.vue
// toDataURLで 'data:image/png;base64' で始まるData URLを返す
this.imageUrl = this.stage.toDataURL
// template内の<img id="img">に作成した画像を表示させる
const img = document.getElementById("img")
img.src = this.imageUrl
作成した画像のData URLをRailsで受け取れるようにする
Vue側のhidden inputにData URLの値をバインドしRailsのform_with
で生成したフォームと紐付ける
views/charts/_form.html.erb
+ <%= form_with model: chart, multipart: true, id: 'form' do %>
<div id="js-chart"></div>
+ <% end %>
chart.vue
+ <input type="hidden" name="chart[image]" id="chart_image" :value="imageUrl">
+ <a @click="save">Save</a>
~中略~
methods: {
+ save() {
+ const promise = new Promise(function(resolve) {
this.imageUrl = this.stage.toDataURL
+ resolve()
+ })
+ function onFulfilled() {
+ const form = document.getElementById('form')
+ form.submit()
+ }
+ promise.then(onFulfilled)
+ }
}
これでSave
ボタンを押下することで画像のData URLをparams
で受け取れるようになりました
受け取ったData URLからActive Storageへ保存する
受け取ったData URLをゴニョゴニョやる必要があるので見通しがよくなるようクラスに切り分けました
image_blob.rb
class ImageBlob
attr_reader :image_data_url
def initialize(image_data_url)
@image_data_url = image_data_url
end
def mime_type
image_data_url[%r/(image\/[a-z]{3,4})/]
end
def to_io
StringIO.new(decoded_content)
end
private
def decoded_content
Base64.decode64(content)
end
def content
image_data_url.sub(%r/data:image\/.{3,},/, '')
end
end
chart.rb
class Chart < ApplicationRecord
has_one_attached :image
def attach_blob(image_data_url)
image_blob = ImageBlob.new(image_data_url)
image.attach(
io: image_blob.to_io,
filename: Time.zone.now,
content_type: image_blob.mime_type #content_typeは無くてもOK
)
end
end
charts_controller.rb
class ChartsController < ApplicationController
def create
@chart = Chart.new(chart_params)
@chart.attach_blob(image_data_url)
if @chart.save
redirect_to @chart, notice: 'Saved!'
else
render :new, status: :unprocessable_entity
end
end
private
def image_data_url
params.require(:chart).permit(:image)[:image]
end
end
以上になります。
色々調べてみたんですが、<input type="file">
でアップロードする方法の記事がほとんどで参考にできるものが少なく苦労しましたがなんとか実装できました。
おそらく作成した画像を直接Active Storageで保存するユースケースは少ないと思いますが誰かの役に立てば幸いです。
リポジトリ:
obregonia1/visible_scratch_skillz
参考:
Active Storage の概要 - Railsガイド
base64でエンコードされた画像をActive Storageで保存する - Qiita
Discussion