- 投稿日:2020-02-08T23:37:23+09:00
プロパティとデータの違いについて
- 投稿日:2020-02-08T23:10:36+09:00
Vue.jsでTodoリストを作ってみた
はじめに
Vue.jsを学習し始めて2週間ほど経ったのでTodoリストを作ってみました!
書き方
Vue.jsは書き方が何種類かありますが、今回はHTML,Javascriptのファイルに分けて書きたいと思います。
ディレクトリツリー
root/ ┝ index.html └ javascript/ └ app.jsHTMLのコード
index.html<!DOCTYPE html> <html lang="ja"> <head> <title>Todoリスト</title> <!-- CDNにてVue.jsを導入 --> <script src="https://unpkg.com/vue@2.5.17"></script> </head> <body> <div id="app"> <!-- 新しいタスクの入力フォーム --> <input type="text" placeholder="新しいタスク" v-model="newTask"> <input type="submit" value="追加" @click="addTask"> <!-- 達成率を表示 --> 達成数: {{ completedTasks }} / {{ totalTasks }} <!-- タスクの一覧 --> <ul> <li v-for="(task, index) in tasks" :key="task.title"> {{ task.title }} <button v-if="task.done" @click="completed(index)">完了</button> <label v-else>完了済み</label> <button @click="deleteTask(index)">削除</button> </li> </ul> </div> <script src="javascript/app.js"></script> </body>Javascriptのコード
app.jsconst tasks = [ { title: 'sample', done: false } ] var app = new Vue({ el: "#app", data: { tasks: tasks, newTask: null }, methods: { completed: function(index) { this.tasks[index].done = true }, addTask: function() { if(newTask){ this.tasks.push({ title: this.newTask, done: false }) this.newTask = null } }, deleteTask: function(index) { this.tasks.splice(index, 1) } }, computed: { completedTasks: function() { var count = 0 for(let i = 0; i < this.tasks.length; i++){ if(this.tasks[i].done){ count++ } } return count }, totalTasks: function() { return this.tasks.length } } })実行結果
まとめ
今回はCDNでお手軽にVue.jsを導入してTodoリストを作成しました。
今後はVueCLIを使用して導入し単一ファイルでアプリケーションを作成したいと思います。
- 投稿日:2020-02-08T22:48:28+09:00
jQueryとVue.jsの比較(表示・非表示+コンポーネント)
今回はjQueryでもよくやる表示・非表示を切り替えるものをVue.jsでもやります。
また、コンポーネントの話にも進みたかったので今回はVue.jsのコンポーネントをCDN版のVue.jsで実施するという内容も含みます。前回までの記事
画面
jQueryの場合
HTMLのBODY部分は以下の通りです。
<div class="container"> <div class="mt-5"></div> <div class="card"> <div class="card-header"> jQueryの場合 </div> <div class="card-body"> <div class="form-group"> <button class="btn btn-primary" id="toggleButton">非表示にする</button> </div> <h5 class="card-title">値を反映</h5> <div id="result"> <p>表示中!!</p> </div> </div> </div> </div>
id="toggleButton"
とIDが振られたbutton
を用意してます。
そのボタンが押されたら「表示する」⇔「非表示にする」を交互に入れ替えます。
また、<div id="result">
内の文字列を「表示中!!」と無し状態を入れ替えます。javascriptは以下の様にします。
<script> $("#toggleButton").click(function() { if ($("#toggleButton").html() === "表示する") { $("#toggleButton").html("非表示にする"); $("#result").append("<p>表示中!!</p>"); } else { $("#toggleButton").html("表示する"); $("#result").empty(); } }); </script>
$("#toggleButton").click()
でボタンがクリックされたイベントをトリガーに、現在ボタンの文字列が何になってるかを確認してボタンの表示内容と<div id="result">
を切り替えるようにしています。Vue.jsの場合
HTMLのBODY部分は以下の通りです。
<div class="container"> <div class="mt-5"></div> <div class="card" id="vue"> <div class="card-header"> Vue.jsの場合 </div> <div class="card-body"> <div class="form-group"> <button class="btn btn-primary" @click="toggle"> <div v-if="mode"> 非表示にする </div> <div v-else> 表示する </div> </button> </div> <h5 class="card-title">値を反映</h5> <div id="result"> <result :mode="mode"></result> </div> </div> </div> </div>javascriptの部分は以下の通りです。
<script> Vue.component('result', { props: {"mode": Boolean}, template: ` <p>{{ getDispValue }}</p> `, computed: { getDispValue: function(){ if(this.mode){ return "表示中!!"; }else{ return ""; } } } }); new Vue({ el: "#vue", data: { mode: true }, methods: { toggle(){ this.mode = !this.mode; } } }); </script>今回はVueのコンポーネントでテンプレートを作成しています。
Vue.component('result', { props: {"mode": Boolean}, template: ` <p>{{ getDispValue }}</p> `, computed: { getDispValue: function(){ if(this.mode){ return "表示中!!"; }else{ return ""; } } } });
props
でコンポーネントが外部から値を受け取る値を指定
- 今回は
mode
という表示を制御するBooleanを受け取ってますtemplate
がテンプレートの中身です
- 今回は
<p>{{ getDispValue }}</p>
としてます{{}}
で囲う箇所はVueで値を埋め込む際のお約束ですcomputed
は前回登場した 算出プロパティ です
getDispValue
がprops
で受け取ったmode
から判断して「表示中!!」を返却するか空を返却するか行ってますnew Vue({ el: "#vue", data: { mode: true }, methods: { toggle(){ this.mode = !this.mode; } } });
- Vueのインスタンスを作ってる箇所で
data
にmode
を持ってますmethods
にはtoggle()
という関数を持ってます
- この
toggle()
はHTML側でボタンに@click="toggle"
としてる箇所から呼ばれますまとめ
今回Vue側のコンポーネントは超ライトな内容にしてるのでコンポーネントのメリットは感じにくいかもしれないです。この程度であれば確かにjQueryで書いたほうが直感的にできてる気がします。ただ、コンポーネント化のメリットは繰り返し同じような表示を行う必要があるときに真価が発揮されるので、繰り返し同じようなボタン制御があるとjQueryの場合はボタンと表示領域のID管理が必要になってくるので重複コードが生まれやすいですが、Vueの場合はコンポーネント内に処理を内包できるのでメンテナンス性の向上も見込めるはずです。
補足
HTML5から
template
が追加されています。(※参考)
ただしIEはこのtemplate
に対応はしていないです。
だからVueのほうがいいのかというとIE11はES5までしか対応できてないのも事実です。(※参考)
なのでIE11のように古いブラウザをサポートするのであればjavascriptの書き方をES5に合わせる、BabelやWebPackのようなトランスパイルを行うなどが必要になってきます。
開発する規模がどれくらいかによってjQueryのほうがいいかもしれないですし、サポートブラウザを明確にしてIE11を捨てるという方法がいいかもしれないですし、トランスパイルしてIE11サポートを頑張る方法もあると思います。
今回は以上です。
jQueryとVueの比較ネタが切れたので次回からは「サーバーにNode.js環境用意できないけどVue.js使いたいからCDN版でなんとか頑張ってみる」方法をまとめていこうかと思います。
- 投稿日:2020-02-08T21:00:04+09:00
JavaScript・TypeScriptのimport・exportの依存関係を可視化するcode-dependencyの紹介
追記情報
- v0.5.0で
.vue
の拡張子もサポートするようになりました。はじめに
現在のJavaScriptは
export
、import
によるモジュールの切り離しと結合が可能であるため。大きなプロジェクトに成長させることができます。実装が進むにつれてファイル間の依存が複雑になっていき、実装全体の依存関係を把握するのが難しくなっていきます。これはプロジェクトに対して新しいメンバーが増えたときに、コードリーディングの時間を十分に取る必要があります。また、OSSのライブラリに貢献したいときも同様の状況が生まれるでしょう。特に後者は開発メンバーが近くにいるとも限らず、他国の方である可能性も十分に高いため開発に参加するための準備が必要になります。
このような、全体の依存関係の設計を見直したい場合や、新たに開発に参画する場合により短時間に理解を深めるためのツールを作成したので紹介します。
@code-dependencyの紹介
DEMO
百聞は一見にしかずと言われるので、DEMOページをご覧ください。code-dependencyは次のような依存関係の表示を生成することができます。
@code-dependencyを利用する
次に実際に@code-dependencyを利用してみましょう。まずはyarn/npmを利用して@code-dependency/cliインストールします。
# npmを使っている人 npm i -g @code-dependency/cli@latest # yarnを使っている人 yarn global add @code-dependency/cli@latest例としてyargs/yargsの依存関係を覗いてみます。
git clone https://github.com/yargs/yargs.git --depth 1 cd yargs code-dependency --source ./ # http://localhost:3000/project を開きますブラウザを開いてみます。ページ左側に走査して取得したファイルツリーが表示されます(.js,.jsx,.ts,.tsx,.vueを対象とします)。
メニューから
yargs/index.js
を選択すると以下のような表示になります。URLの例: http://localhost:3000/project/?pathname=yargs%2Findex.js
可視化によってファイルの依存関係が一目瞭然となります。
ユースケース
依存関係の可視化をしただけなので、ここから何を読み取るかは利用者の自由です。例えば、
- Model/View/Controllerを利用したアーキテクチャが設計通りに参照構造を持っていることを確認する
- ファイル間の循環参照(例:a.js->b.js->c.js->a.js)をしていないことを確認する
- SOLIDの原則に従っているか確認する(インターフェース分離の原則がわかりやすい)
- 新機能を追加するときの依存パターンに既出のものがないか確認する
- チーム内で依存関係の設計の認識合わせをする
などが挙げられます。メンテナンスされているコードや有名なライブラリ(angular.js, react, vscode, etc...)を観察してみて依存関係の研究に勤しむのもよいかもしれません。(他に考えつく利用用途があればぜひコメントで教えて下さい!)
機能紹介
@code-dependency/cliのREADMEに利用可能な機能は書いています。
tsconfigやwebpackを指定できる他、静的にホスティングできるようにHTMLを出力する機能も搭載しています。静的にホスティングする
サンプルとして、
@code-dependency/cli
自体の依存関係を以下にホスティングしています。この出力は次のようなCLIを叩くことで出力されます。
# current directoryはcode-dependency/packages/cli code-dependency --source ./src --exclude node_modules --export-static ./docs --public-path https://himenon.github.io/code-dependency/余談
これは完全に余談ですが、GitHub Pagesにホスティングした状態でLighthouseの結果は以下のとおりです。静的のホスティングされた状態でも快適に使えるように実装しました。
dotからSVGに変換するエンジンを変更する
デフォルトのまま利用した場合、viz.jsを利用してブラウザ側でdot言語をSVGに変換しています。ただこれには欠点があり、viz.jsはMemory Leakしています。依存関係が数百を超えたあたり(具体的にはdot言語で処理するテキストが100KBのオーダーを超えたあたり)からviz.jsでは処理しきれなくなります。
これを避けるために、NativeのGraphvizを利用することで上限を緩和することができます。
Graphvizの公式サイト(http://www.graphviz.org)からマシンにインストールして、dot
コマンドが利用できる状態にしておいてください。この状態で、
--engine dot
のフラグを追加して起動すると、viz.jsを利用しない代わりに、NativeのgraphvizがSVGを生成します。code-dependency --source ./src --engine dotviz.jsはすでにメンテナンスされていない状態なので、Nativeにインストールされているものを利用することをおすすめします。
既知の不具合について
Nativenのdotエンジンを利用していてもSVGが生成されないことがあります。これはNodeJS側のHeap Mmoeryが足りなくなる場合に以下のようなエラーを起こします。
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memoryNode v12以降であればHeapの最大値がハードウェアに依存して変化するので、大規模なプロジェクトでなければそこまで心配ないかもしれません。
code-dependencyが提供するAPIについて
code-dependency自体は特にAPIを提供することはしていません。内部でdependency-cruiserが吐き出すdot言語をSVGに変換し、WEBブラウザ上で容易に確認できるようにしたに過ぎません。
依存関係のテストや詳細な情報を取得したい場合はdependency-cruiserを直接利用すると良いでしょう。
最後に
着想自体は2019年4月あたりにあり、一時は自分でASTを解析してd3jsで依存関係の可視化までフルスクラッチでやっていましたが、対応すべき内容が多くなりメンテナスコストが高くなったため、dependency-cruiserを利用する形にしました。もしこのライブラリがなければ、code-dependencyが誕生しなかったので、ぜひdependency-cruiserにスターをつけてあげてください。
また、code-dependencyの機能自体は大体出揃っており、これほしいな、と思う限り今後の更新頻度は減少ることと思います。その前に宣伝効果の高そうなQiitaに一つ記事を書いてシェアしておこうと思った次第です。もし、既存の状態からより効率的な依存関係の把握や情報共有の方法がありましたらIssueやPull Requestを投げてくださると幸いです。
今回のライブラリのより技術的な話は自分のブログの方に掲載していくかもしれません。興味のある方は足を運んでみてください。
関連するライブラリなどの紹介
dependents-view
URL: https://github.com/Himenon/dependents-view
DEMO: https://himenon.github.io/dependents-view/#packagesnpmライブラリの逆依存関係をGitHubもしくはGitHub Enterpriseから収集し、まとめ直すアプリケーション(自分でビルドする必要がある)。内容としてはGitHubのNetwork Graphと同様ですが、内容をフィルタリングして利用する機能を持っています。自分のライブラリを変更した場合にどのライブラリまで影響を及ぼすか、を検索することができます。
libcheck
URL: https://github.com/Himenon/node-libcheck
yarn.lock(package-lock.jsonは非対応)からライブラリが複数バージョンインストールされていないかチェックするライブラリ。
例1:利用するライブラリの
dependencies
にreactのv15系がインストールされていて、アプリケーション側がdependenciesでreact v16がインストールされているような場合、両方ともnode_modulesに配置されます。これを検出できます。例2:モノレポのようなpackage.jsonを複数扱う場合にdependenciesの更新漏れをチェックすることができます。
# インストール豊富お yarn add -D libcheck # 例えば、reactが複数バージョン入っていないかテストする方法 libcheck --input ./yarn.lock --pattern "react" --testこのページで出てきたリンク一覧
- 投稿日:2020-02-08T18:38:03+09:00
【Vue.js】一部オプションを指定済のコンポーネント作成
例えば、Vuetifyによるボタンのスタイルを、メインの動線(OKなど)とそれ以外(キャンセルなど)で別々にしたいとき、次のようなコードを至るところに入れ込むことになってしまう。
<!-- メイン動線のボタン:背景色と波紋色を指定 --> <v-btn color="primary" :ripple="{ class: 'cyan--text text--lighten-1' }">OK</v-btn>これでは見通しが悪い上、後からスタイルを変更するのも大変である。
解決策
コンポーネントの一部オプションを書き換えて描画をおこなう関数型コンポーネントを作成する。
MainBtn.vue<script> export default { name: 'MainBtn', functional: true, render(h, { data, children }) { data.props = Object.assign({ color: 'primary', ripple: { class: 'cyan--text text--lighten-1' } }, data.props || {}); return h('v-btn', data, children); }, }; </script>使い方
登録すればテンプレートなどに記述できる。
<main-btn>スタイルを変更したボタン</main-btn> <v-btn>そのままのボタン</v-btn>propsやeventsもそのまま使うことができる。
<main-btn @click="onClickOk" elevation="24">OK</main-btn> <v-btn @click="onClickCancel" elevation="24">CANCEL</v-btn>
- 投稿日:2020-02-08T18:13:32+09:00
Gridsome で ブログを作ってみた
こんにちは、おかきょーです。今回は、Gridsome を利用してポートフォリオ兼ブログとしての機能をもつサイトを作ってみました。Netlifyを利用して、無料でサイトを公開、運用する方法を書いて行きたいと思います。
Gridsome とは何か
Gridsome は、Vue.js で利用できる JAMStack フレームワークのひとつです。
普通のVue.js とは異なり、GraphQL を利用してブログ記事のデータを管理するため、
サーバーサイドの設計を行わずにフロントエンドの実装だけでアプリを構築することができます。事前準備
今回、Node.js を利用して 構築していきます。
今回のアプリを構築するにあたり、主に使用したライブラリは次の通りです。
- Gridsome
- Vuetify
- Pug
- Netlify CMS
まず、npm を利用して環境を構築していきます。
$ npm install -g gridsomeインストールが完了したら、
$ gridsome create new-site
と入力して実行します。これにより、Gridsome の開発できる環境が整いました。
Pug を有効にする
私は、Vue で構築するにあたり、Pug が使えるように設定しています。
Pug とは、AltHTML の一つの言語で、以下の例のように、インテントによってHTML の要素を入れ子の状態に
してくれるのが特徴です。例: HTML で実装した場合
<div class="title-head"> <h1> Hello World </h1> </div>例:Pugで実装した場合
.title-head h1 Hello World普通のHTMLと比べて、コードの量が少なくなるだけでなく、各要素が終わりであることを示すために、"</(要素名)>" を書く必要がありません。そのため、修正し忘れることを防いでくれます。
使えるようにするには、
$ npm install -save-dev pug @gridsome/plugin-pugとした上で、
gridsome.config.js
の plugin を次の文を書き足します。
- gridsome.config.js
plugins: [ 'gridsome-plugin-pug', ]Vuetify が使えるように設定する
続いて、Vuetify の設定も行います。Vuetify とは、Vue で利用できるマテリアルデザインコンポーネントフレームワークです。ボタンやテーブルといったコンポーネントがあらかじめ用意されているため、1からデザインの設計をすることなく利用できるのが特徴です。
まず、Vuetify ライブラリと、webpack を編集するライブラリををnpm からインストールします。
$ npm install --save vuetify $ npm install --save-dev webpack-node-externals続きまして、
/src/main.js
に次のように設定します。
- /src/main.js
import DefaultLayout from '~/layouts/Default.vue' // 次のライブラリをimport します。 import Vuetify from 'vuetify'; import 'vuetify/dist/vuetify.min.css' import colors from 'vuetify/es5/util/colors' import 'prismjs/themes/prism-tomorrow.css' export default function (Vue, { router, head, isClient,appOptions }) { Vue.component('Layout', DefaultLayout) // 以下を追加 Vue.use(Vuetify); appOptions.vuetify = new Vuetify({ customVariables: ['~/assets/css/variables.scss'], theme: { dark: false, themes: { dark: { primary: colors.grey.darken4, accent: colors.shades.black, secondary: colors.amber.darken3, info: colors.teal.lighten1, warning: colors.amber.base, error: colors.deepOrange.accent4, success: colors.green.accent3 } } } }) }最後に、
gridsome.server.js
を次のように書いて行きます。
- gridsome.server.jsconst nodeExternals = require('webpack-node-externals'); module.exports = function (api) { // 以下を追加 api.chainWebpack((config, { isServer }) => { if (isServer) { config.externals([ nodeExternals({ whitelist: [/^vuetify/,/\.css$/] }) ]) } }) // [省略] }これにより、Gridsome でVuetify を使えるように設定しました。
Markdown でブログ記事を管理する
このGridsome は、Markdown ファイルで記事の保存、GraphQL を通して記事の読み込みを行います。
マークダウンを利用して記事を利用するために、次のライブラリをインストールします。$ npm install --save @gridsome/source-filesystem @gridsome/transformer-remark $ npm install --save-dev @gridsome/remark-prismjsそして、
gridsome.config.js
にて plugins で次のように設定します。
- gridsome.config.js
plugins: [ 'gridsome-plugin-pug', // 以下を追加 { use: '@gridsome/source-filesystem', options: { path: 'blog/**/*.md', // どのディレクトリのマークダウンを読み込むかを設定 route: "/blog/article/:slugs", // どのURL で公開するか typeName: 'Doc', remark: { plugins: [ '@gridsome/remark-prismjs' // どのCSS を適用させるか ] } } } ]この TypeName では、templates ファイル で設定した Doc.vue をもとにマークダウンが表示されます。
Doc.vue
は次のように実装します。
- Doc.vue
<template lang="pug"> Layout v-container v-layout(row wrap).ma-2 v-flex.ma-3.white(xs12 md10 lg8) h2#document-title.blog-title.pa-2 {{ $page.doc.title }} v-divider .markdown(v-html="$page.doc.content") v-flex.ma-3(md1 lg3) right-sidebar v-btn(color="error")(fab bottom right fixed) v-icon(large) mdi-chevron-up </template> <page-query> query Doc ($path: String!) { doc: doc (path: $path) { title path date (format: "D. MMMM YYYY") timeToRead content } } </page-query> <script> import rightSidebar from '../components/templates/Sidebar/rightSidebar.vue' export default { components:{ rightSidebar }, metaInfo() { return { title: this.$page.doc.title, meta: [ { key: 'description', name: 'description', content: this.$page.doc.description } ] } } } </script> //[省略]以上の設定により、読み込んだ Markdown ファイルをブログ記事として公開することができるようになりました。
Netlify CMS を追加する
記事を編集するにあたり、Git を利用して記事を管理することもできますが、今回はNetlify CMS を利用して記事編集ができるようにしたいと思います、
Netlify CMS については次の記事を参照してください。
このCMS が使えるようにするには、npm にて、次のコマンドを実行します。
$ npm install --save gridsome-plugin-netlify-cmsCMS を有効にするには、src ファイルないに, admin ディレクトリを構築します。
admin ディレクトリ内に、config.yml, index.html, index.js
を次のように設定します。
- config.yml
backend: name: github repo: okakyo/my-gridsome-site media_folder: "static/uploads" public_folder: "/uploads" publish_mode: editorial_workflow collections: - name: "blog" label: "Blogs" folder: "blog" create: true slug: "blog/{{fields.slugs}}" identifier_field: title fields: - {label: "Title", name: "title", widget: "string"} - {label: "Tag", name: "tags", widget: "list"} - {label: "Slug", name: "slugs", widget: "string"} - {label: "img", name: "thumbnail",widget: "image"} - {label: "Publish Date", name: "date", widget: "date"} - {label: "Content", name: "body", widget: "markdown"}
- index.html
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Netlify CMS</title> </head> <body> <script src="index.js" type="module"></script> </body> </html>
- index.js
import CMS from "netlify-cms"このファイルの設定が完了したら、 GitHub でOauth 認証を有効になるよう設定します。
Netlify の管理画面で、
"Settings" ”→ ”Identity” → ”Enable Git Gateway” をクリックして、GitHub での認証ができるようにします。これで、Netlify CMS を使用できるようにします。
タグ機能を追加
タグ機能を有効にするには、マークダウンで編集した
gridsome.config.js
のplugin に以下のコードを追加してください。
下に書かれてある,refs
の要素を設定することで、タグ機能を利用することができます。plugins: [ 'gridsome-plugin-pug' { use: '@gridsome/source-filesystem', options: { path: 'blog/**/*.md', route: "/blog/article/:slugs", typeName: 'Doc', // こちらを追加 refs: { tags: { typeName: "Tag", route: "/tag/:id", create: true } }, remark: { plugins: [ '@gridsome/remark-prismjs' ] } } },]以上で、Gridsome を利用する際、独自に設定しなくてはならないことを書きました。
あとは、Vue.js と同じ要領で コンポーネントを設計して行きます。参考サイト
- 投稿日:2020-02-08T17:36:04+09:00
webpack + Vue(webpack.config.js書き方の例)
webpack + Vue
自分用メモ
はじめに
webpackはモジュールバンドラーである。エントリーポイントに設定したjsファイルを中心に、各モジュールのjsファイルを纏めてバンドル
- モジュール管理できるため機能ごとに開発ができ保守性が上がる
- 複数のjsファイル(cssや画像も)を一つのファイルにbundleできるためリクエスト数減少とパフォーマンス向上 ※ ファイルサイズの増大によるパフォーマンス低下もありうる
- 依存関係の解消
1. webpack使用の下準備
1.1. webpackでの基本的なパッケージのインストール
$ npm init -y $ npm i -S vue # .vueファイルを読み込むために必須 $ npm i -D vue-loader vue-template-compiler $ npm i -D webpack webpack-cli $ npm i -D terser-webpack-plugin optimize-css-assets-webpack-plugin # .vueファイルではCSSも扱うので必須 $ npm i -D node-sass css-loader $ npm i -D babel-loader @babel/core @babel/preset-env # 分割したcssの読み込みをリロードしない(brouser-sinc代用) $ npm i -D webpack-dev-server # gulp使うとき $ npm i -D gulp gulp-eslint gulp-notify gulp-plumber1.2. package.jsonの編集
package.json{ "name": "webpack_test", "version": "1.0.0", "description": "", - "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" // sassも監視対象 + "build:dev": "webpack --mode development --watch", + "build": "webpack --mode production", // dev-serverだけだとjsの監視はされるが、バンドルはされない // 保存すると自動リロードがかかるため更新はされるが、監視よりも遅いため二回目リロード時にバンドルが反映される + "start": "webpack --mode development --watch & webpack-dev-server", }, "scripts": { }, + "private": true, "keywords": [], "author": "", "license": "ISC", "dependencies": { "jquery": "^3.4.1" }, "devDependencies": { "@babel/core": "^7.4.5", "@babel/polyfill": "^7.4.4", "@babel/preset-env": "^7.4.5", "autoprefixer": "^9.6.0", "babel-loader": "^8.0.6", "css-loader": "^3.0.0", "mini-css-extract-plugin": "^0.7.0", "node-sass": "^4.12.0", "optimize-css-assets-webpack-plugin": "^5.0.3", "postcss-loader": "^3.0.0", "sass-loader": "^7.1.0", "style-loader": "^0.23.1", "terser-webpack-plugin": "^1.3.0", "webpack": "^4.35.2", "webpack-cli": "^3.3.5", "webpack-dev-server": "^3.7.2" } }1.2. webpack.config.jsの作成
webpackをインストールしたら
webpack.config.js
を手動で作成webpack.config.js// 開発or本番モードの選択(development、production、noneのいずれか設定必須) // development: 開発時のファイル出力のモード(最適化より時間短縮,エラー表示などを優先) // production: 本番時のファイル出力のモード(最適化されて出力される) const MODE = "development"; // ソースマップの利用有無(productionのときはソースマップを利用しない) const enabledSourceMap = MODE === "development"; // ファイル出力時の絶対パス指定に使用 const path = require('path'); // プラグイン // js最適化 const TerserPlugin = require('terser-webpack-plugin'); module.exports = { // エントリーポイント(メインのjsファイル) entry: './src/js/app.js', // ファイルの出力設定 output: { // 出力先(絶対パスでの指定必須) path: path.resolve(__dirname, 'dist/js'), // 出力ファイル名 filename: "bundle.js" }, mode: MODE, // ソースマップ有効 devtool: 'source-map', // ローダーの設定 module: { rules: [ { test: /\.css$/, use: ["vue-style-loader", "css-loader"] }, { test: /\.vue$/, loader: "vue-loader" }, { // ローダーの対象 // 拡張子 .js の場合 test: /\.js$/, // ローダーの処理対象から外すディレクトリ exclude: /node_modules/, // Babel を利用する loader: "babel-loader", // Babel のオプションを指定する options: { presets: [ // プリセットを指定することで、ES2019 を ES5 に変換 "@babel/preset-env" ] } } ] }, // import 文で .ts ファイルを解決するため resolve: { // Webpackで利用するときの設定 alias: { vue$: "vue/dist/vue.esm.js" }, extensions: ["*", ".js", ".vue", ".json"] }, plugins: [ // Vueを読み込めるようにするため new VueLoaderPlugin() ], // mode:puroductionでビルドした場合のファイル圧縮 optimization: { minimizer: production ? [] : [ // jsファイルの最適化 new TerserPlugin({ // すべてのコメント削除 extractComments: 'all', // console.logの出力除去 terserOptions: { compress: { drop_console: true } }, }), ] }, // js, css, html更新時自動的にブラウザをリロード devServer: { // サーバーの起点ディレクトリ // contentBase: "dist", // バンドルされるファイルの監視 // パスがサーバー起点と異なる場合に設定 publicPath: '/dist/js/', //コンテンツの変更監視をする watchContentBase: true, // 実行時(サーバー起動時)ブラウザ自動起動 open: true, // 自動で指定したページを開く openPage: "index.html", // 同一network内からのアクセス可能に host: "0.0.0.0" } };2. webpackの処理対象となるjsファイルの書き方
エントリーポイントとなるjsファイルを
app.js
モジュールファイルをmodule1.js
とすると次のように書くjsファイル内で
Vue.component()
などのメソッドで作る場合と、.vue
拡張子の単一ファイルコンポーネントで作る場合がある
以下はコンポーネントごとに.vue
拡張子で作るときであるapp.jsimport Vue from 'vue'; // コンポーネントファイルがある場合 import App from './components/App.vue';./components/App.vue// 関数を定義。
index.html<!DOCTYPE html> <html lang="ja" dir="ltr"> <head> <meta charset="utf-8"> <title>webpack_sample</title> <link rel="stylesheet" href="./dist//css/main.css"> </head> <body> // <script src="./dist/js/bundle.js"></script> </body> </html>3. webpack実行
実行コマンド
package.json
のscripts
で設定した名称で実行$ npm run build:dev # バンドル(開発用)実行 $ npm run build # バンドル(本番用)実行 $ npm run start # バンドルと監視スタート
- 投稿日:2020-02-08T17:26:18+09:00
Vue.jsの基礎
Vue.jsの基本的な使い方
- Reactは単方向データバインディングで一方通行なのに対し、Vueは双方向データバインディングで双方向にデータを流す仕組みになっている。
- 双方向データバインディングの場合は
js
の中身が変わったらすぐに反映される- 単方向データバインディングの場合は
render
などの描画の処理が単方向であるため即時反映とはならない環境構築 ---未完成
vue.esn.js
というtemplate機能のないランタイム限定ビルドがあるので、これも必要に応じてnpmでインストールする基本的な書き方
new Vue
でインスタンス生成el
でセレクターを書き込み、スコープを指定するdata
プロパティはオブジェクトの形で自分が使いたいプロパティを定義する
- データオブジェクトの中の値を出し入れするための入れ物のようなもの
data
で定義したプロパティはhtml
で使うことができるファイルのベースづくり
app.jsimport Vue from 'vue' new Vue({ el: '#app1', data: { message: 'vueのテンプレートの構文。{{}}で囲って処理がかける' } })htmlの方では
{{}}
の中でテンプレートの構文やif文など、またインスタンス化したdataのプロパティなども使えるindex.html<div id="app1"> {{ message }} </div>v-bindを使った属性のバインド
- 単方向のデータバインディング
- dataの変更に応じて表示されるが、HTML側の入力でdataが変更されることはない
v-bind:
とすることでインスタンスで定義したdata
のプロパティが属性として出力されるv-bind:
は省略可能で、:
のみで書くことができる- class属性を渡すときは連想配列の形で渡す
- その連想配列の値が
true
かfalse
かでプロパティ名が付くどうかが判別される- この例の場合、
active
とtext-danger
がtrue
なのでclass="active text-danger
として入ってくる※
text-danger
のように-
を使っている場合''
または""
で囲む必要があるapp.jsimport Vue from 'vue' new Vue({ el: '#app2', data: { message: 'このページをロードしたのは ' + new Date().toLocalString(), classObject: { active: true, 'text-danger': true } } })index.html<div id="app2"> <!-- title属性は補足的な情報を与えるときに使用し、ポインタを重ねると吹き出し(ツールチップ)が表示される --> <span v-bind:title="message" :class="classObject"> この文字にロードした日付が表示される </span> </div>v-if, v-else-if, v-elseを使った条件分岐で表示非表示
v-ifのみでの条件分岐
app.jsimport Vue from 'vue'; new Vue({ el: '#app3', data: { isShow: true } })
isShow
の名称は任意v-if
の中でtrue
かfalse
かを判定し、true
であれば、そのタグ自体を表示し、false
であれば、そのタグ自体を非表示にする ※ 非表示の場合DOM
自体がなくなる(DOM
の残るdisplay:none
とは異なる)htmlindex.html<div id="app3"> <span v-if="isShow">v-ifを使ったDOMの表示非表示</span> </div>v-if, v-else-if, v-elseを使った条件分岐
app.jsnew Vue({ el: '#app3', data: { isShow: 'a' } })index.html<div id="app3"> <span v-if=" isShow === 'a' ">v-ifを使った条件分岐</span> <span v-else-if=" isShow === 'b' ">v-ifと同階層に書く</span> <span v-else>v-ifと同階層に書く</span> </div>v-showを使った要素の表示非表示
v-show
による要素は常に描画され、v-if
と異なりDOMを維持する- styleだけ非表示になっているだけ
(display: none;)
v-show
がtrue
なら表示、false
なら非表示(display:none;
)app.jsnew Vue({ el: '#app3', data: { isShow: true } })index.html<div id="app3"> <span v-show="isShow">v-showで表示</span> <span v-show="!isShow">v-showで非表示に</span> </div>v-forを使ったループ処理
v-for
の値に変数名を割り当てる(今回はtodo
)
todo in todos
とすることでtodo
にjsファイルで定義したtodos
の一つ一つの値のデータが入る
data
プロパティの値を配列リテラルの形式で渡すことで、配列一つ一つを回せるapp.jsimport Vue from 'vue'; new Vue({ el: '#app4', data: { todos: [ {text: 'v-forで'}, {text: 'htmlを'}, {text: 'ループ生成'} ] } })index.html<div id="app4"> <ol> <li v-for="todo in todos"> {{ todo.text }} </li> </ol> </div> <!-- 出力結果 --> <ol> <li>v-forで</li> <li>htmlを</li> <li>ループ生成</li> </ol>v-onを使ったイベント発火
v-on:
のあとにイベント名を指定し、イベント後の発火したいメソッドを指定
v-on:
は省略可能で、@
のみで書くことができる
- vueでは
methods
プロパティを定義でき、viewの中で使いたい関数を定義する
- methodsは関数の置き場所として用いられる
- methodsは関数なので、呼べばそのまま実行される
- methodsで算出された値はキャッシュされない
changeMessage
という関数名は任意methods
プロパティ内でthis
を使うとdata
プロパティにアクセスできる※
this.message
でmessage
にアクセスできるapp.jsimport Vue from 'vue'; new Vue({ el: '#app5', data: { message: 'Hello Vue.js' }, methods: { changeMessage: function() { this.message = this.message + '変更しました' } } })index.html<div id="app5"> <p>{{ message }}</p> <button v-on:click="changeMessage">メッセージを変える</button> </div> <!-- 出力結果 --> メッセージを変える <!-- クリック --> Hello Vue.js変更しましたv-onでtoggle
v-on
では、必ずしもmethodを使わなければいけないというわけではない
以下はtoggle処理の例である<div id="app5"> <button v-on:click="show = !show"> Toggle </button> </div>v-model 双方向データバインディング
- 双方向のデータバインディング
- dataの変更に応じて表示され、HTMLでフォームの入力を行った際にもdataの値の変更が可能
input
要素やtextarea
要素、select
要素に双方向 (two-way) データバインディングを作成v-model
でdata
のプロパティを指定すると、jsファイルのプロパティと描画が連携される- 双方向データバインディングでは入力フォームの値が変わると描画も変わるため、
p
タグの中身もすぐに反映される- 下の例ではinputタグの中にmessageの値がvalueの形で入る
- ユーザーの入力データが自動で更新される
app.jsimport Vue from 'vue'; new Vue({ el: '#app6', data: { message: '双方向データバインディング' } })index.html<div id="app6"> <p>{{ message }}</p> <input type="text" v-model="message"> </div>computed 算出プロパティ
computed
は関数の処理を書く算出のプロパティであるmethods
と似ているが、computed
はdata
プロパティの変更に依存して描画に反映される- dataやcomputedの値が変化することで自動的に実行
computed
はjsの方で算出された値が常に結果がキャッシュされており再利用できる。data
の変更(this
の変更)をthis
で監視しているため、data
プロパティに変更があった場合のみ描画に反映される
this
を持たないcomputed
はdata
の変更を参照できず、ずっと初回のキャッシュを表示し続けるmethods
は再描画されるたびに処理が実行されるapp.jsimport Vue from 'vue'; new Vue({ el: '#app7', data: { isShow: true } computed: { showString: function() { return (this.isShow) ? Date.now() : 'isShowはfalse' }, showString2() { return Date.now() } }, methods: { showStringMethods() { return (this.isShow) ? Date.now() : 'isShowはfalse' }, showStringMethods2() { return Date.now() } } })
computed
の場合は()
は不要index.html<div id="app7"> <input type="checkbox" v-model='isShow'> <p>isShow: {{ isShow }}</p> <p>showString: {{ showString }}</p> <p>showString2: {{ showString2 }}</p> <p>showStringMethods: {{ showStringMethods() }}</p> <p>showStringMethods2: {{ showStringMethods2() }}</p> </div> <!-- 出力結果 --> isShow: true showString: 123456789 showString2: 123456789 showStringMethods: 123456789 showStringMethods2: 123456789 <!-- checkbox click --> isShow: true <!-- 関数内でthisをを持つため、dataの変更を監視して反映 --> showString: isShowはfalse <!-- dataが変更されてもthisがないので反映されず、初回のキャッシュ内容保持 --> showString12: 123456789 <!-- methodsはdataに依存せず毎回再描画がされるため更新される --> showStringMethods: isShowはfalse showStringMethods2: 234567890v-htmlでサニタイズを無効化
vue.jsでは自動でサニタイズされ、タグは文字列として読み込まれる
タグとして読み込ませたい場合はサニタイズを無効化するv-html
を使うapp.jsimport Vue from 'vue'; new Vue({ el: '#app10', data: { script: '<p style="color:red">タグとして表示</p>' } })index.html<div id="app10"> <p>{{ script }}</p> <p v-html="script"></p> </div> <!-- 出力結果 --> <p style="color:red">タグとして表示</p> タグとして表示 (color:red; 適用)トランジションとアニメーション
アニメーションには
Enter
アニメーションとLeave
アニメーションがある
Enter
: 非表示から表示に変わるときのアニメーション
- 変化前の状態のclassは
v-enter
, 変化後のclassはv-enter-to
- 変化過程にアニメーションを設定したいときのclassは
v-enter-active
Leave
: 表示から非表示に変わるときのアニメーション
- 変化前の状態のclassは
v-leave
, 変化後のclassはv-leave-to
- 変化過程にアニメーションを設定したいときのclassは
v-leave-active
transition
タグでname
属性を付けた場合はv-
の箇所をその名前に変更できるapp.jsimport Vue from 'vue'; new Vue({ el: '#app11', data: { show: true } })index.html<style> .fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to { /* .fade-leave-active below version 2.1.8 */ opacity: 0; } </style> <div id="app11"> <button v-on:click="show = !show"> Toggle </button> <transition name="fade"> <p v-if="show">hello</p> </transition> </div>コンポーネントの使い方
コンポーネントとは名前付きの再利用可能な
Vue
インスタンスである(部品のようなもの)
Vue.component
でコンポーネントの登録
- 第一引数にコンポーネント名を指定する、第二引数にコンポーネントに定義したい値(
data
,template
など)を設定する- コンポーネントで
data
を定義するときは関数で定義する ※ 関数ではなくオブジェクトの形にすると複数のコンポーネントで値を共有する形になる。関数にすることで、個々のコンポーネントに値をもたせることができるコンポーネント登録後に
new Vue
でインスタンス化するapp.js// 登録する Vue.component('sample-component' { template: '<div>this is sample</div>' }) // インスタンス生成 new Vue({ el: '#app' })コンポーネントを挿入したいところに呼び出すコンポーネントのタグを記述し、
app.js
を読み込むsample.html<div id="app"> <sample-component></sample-component> </div> <script src="app.js"></script>HTMLが置換され、次のようになる
<div id="app"> <div>this is sample</div> </div>templateについて
temaplate
はコンポーネントのHTMLを渡す
htmlファイルでtempalte
のタグが挿入されるapp.jsVue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">clicked {{ count }} times</button>' }) new Vue({el: '#app12'})コンポーネント名
button-counter
にコンポーネントが割り当てられ、template
のHTMLが挿入されるindex.html<div id="app12"> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> </div>コンポーネントの登録を省略
slot
を使用していなければ下記のように省略できる// 省略なし <template> <div id="app"> <app-header></app-header> </div> </template> // 省略あり <temaplte> <div id="app"> <app-header /> </div> </template>単一ファイルコンポーネントを使うとき
app.js// 登録する Vue.component('sample-component', require('./components/SampleComponent.vue').default); // インスタンス生成 new Vue({ el: '#app' })./components/SampleComponent.vue// template作成 <template> // </template> // このtemplateを使ったときの共通の処理を追記する <script> export default { data: { // }, mounted() { // } } </script>propsで親コンポーネントから受け継ぐ
props
を使うことでコンポーネントの親から子へ値を受け渡せるprops
は配列の形式で属性を定義し、親コンポーネントからその属性値を受け継いでdata
のプロパティのように振る舞うapp.jsVue.component('blog-post', { props: ['title'], template: '<h3>{{ title }}</h3>' }) new Vue({el: '#app13'})index.html<div id="app13"> <!-- 親コンポーネント(コンポーネントの受け渡し先となる生成元のタグのこと) --> <!-- このタグを親コンポーネントといい、作成したコンポーネントを子コンポーネントという --> <blog-post title="title属性をpropsで渡せる"></blog-post> <blog-post title="title属性をpropsで渡せる"></blog-post> <blog-post title="title属性をpropsで渡せる"></blog-post> </div>オブジェクトでない配列を指定する場合
- 子が親からデータを受け取るとき、配列で指定するか、オブジェクトの配列として指定するかの2通りある
- 簡潔に列挙できるが、データの情報が記述できず、itemもvalueも同様に扱われる
parent.vue<child :param0="item", :param1="value" >child.vueprops: [ 'item', 'value' ]オブジェクトの配列として受け取る場合
- 詳細に記述できるため、違いを明記できる
child.vueprops: { item: { type: Object }, value: { type: Number, default: 0 } }emitで子から親へデータを伝達
- 子から親へのデータの伝搬では$emitと$onを一対として使用するカスタムイベントを活用する
- 親コンポーネントからこのイベントを監視する場合は
v-on:
,@
でイベント名を紐付けて使用する子コンポーネント
child.vue<template> <div class="item"> <span>{{ value }}</span> <button @click="updateEvt">追加</button> </div> </template> <script> export default { data() { return { value: 0 } }, methods: { plus0ne() { this.value += 1 // $emitの第2, 第3...引数で変数を指定するとその変数を親が受け取ることができる this.$emit('update', this.value) } } } </script>親コンポーネント
parent.vue<template> <counter @updated="updateEvt" /> </template> <script> import Child from './Child.vue' export default { components: { Child }, methods: { // 子から変数を受けとることができる updateEvt(value) { console.log('The value updated' + value) } } } </script>イベントとメッセージを親コンポーネントに渡す方法
v-on:click=$emit('カスタムイベント名')
のように$emit
で任意のカスタムイベント名を指定することで、そのイベントをイベント名として親子コンポーネントで通知することができる
- この例では
v-on:カスタムイベント名=func()
とするとclick
イベントが通知される
template
はバッククォートで囲むことで階層構造で文字列を書くことができる(ES6より)app.jsVue.component('blog-post', { props: ['post'], template: ` <div class="blog-post"> <h3>{{ post.title }}</h3> <button v-on:click="$emit('enlarge-text')"> Enlarge text </button> <div v-thml="post.content"></div> </div> ` }) new Vue({ el: '#app14', data: { posts: [ { id: 1, title: 'sample post1', content: '<p>サンプル投稿のコンテント</p>' }, { id: 2, title: 'sample post2', content: '<p>サンプル投稿のコンテント</p>' }, { id: 3, title: 'sample post3', content: '<p>サンプル投稿のコンテント</p>' }, ] } })index.html<div id="app14"> <div id="blog-posts-events-demo"> <div :style="{ fontSize: postFontSize + 'em' }"> <blog-post v-for="post in posts" v-on:enlarge-text="fontSizeScale()" v-bind:key="post.id" v-bind:post="post" ></blog-post> </div> </div> </div>
- 投稿日:2020-02-08T16:23:51+09:00
Nuxtでvue-carouselを使用していたら'ReferenceError: window is not defined'が出たお話
環境
nuxt 2.4.0
vue-carousel 0.18.0現象
nuxtにvue-caroucelを実装したが、下記のようなエラーになり
リロード時に画面がずっと読み込み状態になってしまった。ReferenceError: window is not definedどうやたSSR時に、
document
やwindow
にアクセスすると上記のエラーが発生するみたい実装方法
~plugin/vue-caroucel.jsimport Vue from 'vue' import VueCarousel from 'vue-carousel' Vue.use(VueCarousel)nuxt.config.jsplugins: [ { src: '~/plugins/vue-carousel', mode: 'client' } ],index.vue<template> <div class="container"> <carousel :navigation-enabled="true" :per-page="1"> <slide v-for="(url, key) in images" :key="key"> <div class="product-img"> <img class="product-card-img" :src="url" /> </div> </slide> </carousel> </div> </template> <script> import { Carousel, Slide } from 'vue-carousel' export default { layout: 'client/simple', components: { Carousel, Slide }, data() { return { hasError: false, images: [ require('@/assets/img/NoImage.png'), require('@/assets/img/NoImage.png'), require('@/assets/img/NoImage.png'), require('@/assets/img/NoImage.png'), require('@/assets/img/NoImage.png'), require('@/assets/img/NoImage.png') ] } }, methods: { async login() {} } } </script>解決方法
各画面でのインポートのを下記のように書き換える
- import { Carousel, Slide } from 'vue-carousel' + import Carousel from 'vue-carousel/src/Carousel.vue' + import Slide from 'vue-carousel/src/Slide.vue'まとめ
vueのモジュールをNuxtで使用するには色々とトラブルがあってなかなか大変です。。。
SSRについてしっかり理解して進めないと色々ハマっていきそうだと思いました。
- 投稿日:2020-02-08T01:16:14+09:00
【PWA】YouTubeをみんなでワイワイ見るためのサービスをローンチしました!!
みなさんこんにちは!
今回はYouTubeの動画を複数人で同時視聴できるサービスを作りましたので紹介したいと思います。
ブラウザを開くだけで、友達や恋人と同じ動画を見ることができます!複数人で同じ動画を見て、あーでもないこーでもないと意見を交わすのはめちゃめちゃ楽しいです。
これはぜひ皆さんにも体験してもらいたいです!YouTube同時再生サービス DJ7
https://www.dj7.io初回のアクセスは音がならないように設定してありますのでご安心ください。
できること
同期再生
サービスの要です。複数のデバイス間で再生の状態が同期され、離れている場所でも同じタイミングで同じ動画を視聴できます。
右下をクリックすると動画を画面に大きく表示します。これによって複数人で同じ動画を見ることができます?シークバー共有
DJ7ではシークバーの状態もユーザ間で共有されます。これはYouTube Liveでは得られない体験です。
ユーザが自由に操作できて、かつ状態が共有されるサービスは他にないと思います笑
DJ7ではユーザができることに優劣がないので全員が同じ操作をすることができます。動画の検索
YouTube上の動画を検索して、一番関連度が高いものを追加でします。もちろんURLを直接入れたり、プレイリストの追加にも対応してます!
ちなみにテキストボックスの上のアイコンは同じ部屋にいるユーザです割り込み
あまり馴染みがない機能だと思います。例えば長い動画を流しているときに有効です。
再生中の動画とキューの動画を入れ替えることができます。入れ替わった動画はキューの一番上に追加され、どこまで再生されていたかなどの情報が保存されます。曲の順番の入れ替え
DJ7には再生待ちの動画のリストがあり、その順番をドラッグ・アンド・ドロップで変えることができます。
ちなみにDJ7では再生待ちのリストのことをQueueと呼んでいます。履歴
再生した動画は履歴に保存されるようになっており、そこからも曲の追加ができます。
履歴の保存はログインが必要ですので、その点だけご了承ください。部屋の作成
自分で部屋を作成して、誰かを呼ぶことも一人で使うこともできます。適当な部屋名を入れてJumpしていただくだけで部屋が作成されます。
こちらの機能もログインが必要です。DJ7で可能になること
もともと、DJ7はDiscordのMusicBotと同じことがWebでできるようになることを目指して開発していました。
ちなみに、MusicBotとはDiscordの同じチャンネルにいる人同士で音楽を聞くためのサービスです。しかし、MusicBotには一度に曲を複数追加できない、そもそもインストールがめんどくさいなど、私の中で数多のフラストレーションがありました。
そこでもっとカジュアルに同時視聴を楽しみたいという思いのもと今回のDJ7を開発しました。最初はMusicBotの代替を目指していたのでYouTubeの動画を読み込んでいても、音楽を聞くことに専念するアプリにしようと考えていました。
しかし、複数人で同じ動画を視聴することが想像以上に楽しい体験だったので結局残すことにしました笑複数人で同じ動画を見ることができるようになり、以下のようなことが可能になります。
同じ動画を、同じタイミングで見て意見を交わす
これは最初にも書きましたね。これは誰かと通話していることが前提となっていますが、ぜひやってみてほしいです。
生放送を見るのとはまた違った楽しみがあります。DJ7に集まって作業用BGMを流す
これが元々のMusicBotがメインでできたことになります。MusicBotとは違い通話する必要はないので、よりカジュアルに参加でき、自分の作業にも集中することができます。
また、この用途ではリアルで集合したときにも使えます。どこかに集まったときにBGMを流す役を人を立てるけど、自分が流したい曲があったときに皆さんはどうしてましたか? DJ7ではいちいちリクエストして流してもらうという手間はいりません。動画を共有する
なにか面白い動画があったときにURLで共有することがあると思います。DJ7ではURLを共有する手間も、URLをクリックして開いてもらう手間も必要ありません。
動画を流してリモートで授業
これはやったことがありませんが、可能だと思います。同じ動画をみて、重要な点は一時停止して解説をするといったことができます。
ほかにもこんな使い方はどうですか?
- オンラインでカラオケ
- お笑いのライブを一緒にみて笑う
- ライブコーディングの同時視聴
- 製品発表や商品レビューをみんなで思ったことを言いながら視聴
- 勉強会
私が知らないだけで使い方はもっとたくさんあると思います。ぜひみなさん思い思いの使い方をしてみてください!!
開発スケジュール
今回の開発スケジュールは以下のような感じです。
工程 工数 デモ作成 3日程度 ロジック実装 10日程度 デザイン 10日程度 その他 7日程度 最初の3日で同時視聴ができるデモを簡単に作り、知り合いに見せました。このときポジティブな感想をもらえたのがモチベーションになりました。
そこから仲間内で実際に1ユーザとして使ってもらい意見をもらいながら開発を進めました。
また、1ヶ月で作るということを最初に決めていましたので、機能の実装は最低限必要なものだけに絞り、機能を追加するよりは、すでにある機能のブラッシュアップを優先させること常に意識していました。次回は今回学んだことを活かして、もっと早くサービスのローンチまで漕ぎ着けたいです。
技術スタック
今回はフロントエンドにVue、バックエンドにFirebaseを使いました。
とくに同期再生といった根幹の部分はFirestoreだけで実装されています。
Cloud Functionsなども使いませんでしたので、コーディングはフロントエンドだけに集中できました。Firebase最高。
苦労した点
モバイルについてです。AndroidやiOSでは、OS側の制約により、YouTubeの音をミュートにしなければサイトを訪れたときに自動で再生されません。そのため、サイトを訪れてから最初にYouTubeのプレイヤーを操作していただきミュートを解除していただく必要があります。この点だけご了承していただきたく・・・
また、今回の場合ではあまり難しいロジックはありませんでしたので、実装自体にはあまり苦労しませんでした。
まとめ
今回は公開のためにあまり機能をつけませんでしたが、私自身欲しい機能がまだまだたくさんあるのでバージョンアップを重ねていきたいと思います。
技術についての詳しい話や、サービスの途中経過も適時報告したいと思います。質問はTwitter @imataka7 または @dj7app までお願いします!
また、こちらのDiscordのサーバでWebサービスを個人開発する人たちで切磋琢磨するコミュニティを運営しています!
Dj7のリリースに至るまでこのコミュニティで沢山のフィードバックを貰えたのはかなり大きかったです。
Webサービスをこれから作りたいという初心者でも大歓迎なので気軽に参加してみて下さい!
https://discord.gg/hNWjDBdYouTube同期再生プラットフォーム DJ7 https://www.dj7.io
- 投稿日:2020-02-08T01:16:14+09:00
【PWA】YouTubeをみんなでワイワイ見るためのサービス、DJ7をローンチしました!
みなさんこんにちは!
今回はYouTubeの動画を複数人で同時視聴できるサービスを作りましたので紹介したいと思います。
ブラウザを開くだけで、友達や恋人と同じ動画を見ることができます!複数人で同じ動画を見て、あーでもないこーでもないと意見を交わすのはめちゃめちゃ楽しいです。
これはぜひ皆さんにも体験してもらいたいです!YouTube同時再生サービス DJ7
https://www.dj7.io初回のアクセスは音がならないように設定してありますのでご安心ください。
できること
同期再生
サービスの要です。複数のデバイス間で再生の状態が同期され、離れている場所でも同じタイミングで同じ動画を視聴できます。
右下をクリックすると動画を画面に大きく表示します。これによって複数人で同じ動画を見ることができます?シークバー共有
DJ7ではシークバーの状態もユーザ間で共有されます。これはYouTube Liveでは得られない体験です。
ユーザが自由に操作できて、かつ状態が共有されるサービスは他にないと思います笑
DJ7ではユーザができることに優劣がないので全員が同じ操作をすることができます。動画の検索
YouTube上の動画を検索して、一番関連度が高いものを追加でします。もちろんURLを直接入れたり、プレイリストの追加にも対応してます!
ちなみにテキストボックスの上のアイコンは同じ部屋にいるユーザです割り込み
あまり馴染みがない機能だと思います。例えば長い動画を流しているときに有効です。
再生中の動画とキューの動画を入れ替えることができます。入れ替わった動画はキューの一番上に追加され、どこまで再生されていたかなどの情報が保存されます。曲の順番の入れ替え
DJ7には再生待ちの動画のリストがあり、その順番をドラッグ・アンド・ドロップで変えることができます。
ちなみにDJ7では再生待ちのリストのことをQueueと呼んでいます。履歴
再生した動画は履歴に保存されるようになっており、そこからも曲の追加ができます。
履歴の保存はログインが必要ですので、その点だけご了承ください。部屋の作成
自分で部屋を作成して、誰かを呼ぶことも一人で使うこともできます。適当な部屋名を入れてJumpしていただくだけで部屋が作成されます。
こちらの機能もログインが必要です。DJ7で可能になること
もともと、DJ7はDiscordのMusicBotと同じことがWebでできるようになることを目指して開発していました。
ちなみに、MusicBotとはDiscordの同じチャンネルにいる人同士で音楽を聞くためのサービスです。しかし、MusicBotには一度に曲を複数追加できない、そもそもインストールがめんどくさいなど、私の中で数多のフラストレーションがありました。
そこでもっとカジュアルに同時視聴を楽しみたいという思いのもと今回のDJ7を開発しました。最初はMusicBotの代替を目指していたのでYouTubeの動画を読み込んでいても、音楽を聞くことに専念するアプリにしようと考えていました。
しかし、複数人で同じ動画を視聴することが想像以上に楽しい体験だったので結局残すことにしました笑複数人で同じ動画を見ることができるようになり、以下のようなことが可能になります。
同じ動画を、同じタイミングで見て意見を交わす
これは最初にも書きましたね。これは誰かと通話していることが前提となっていますが、ぜひやってみてほしいです。
生放送を見るのとはまた違った楽しみがあります。DJ7に集まって作業用BGMを流す
これが元々のMusicBotがメインでできたことになります。MusicBotとは違い通話する必要はないので、よりカジュアルに参加でき、自分の作業にも集中することができます。
また、この用途ではリアルで集合したときにも使えます。どこかに集まったときにBGMを流す役を人を立てるけど、自分が流したい曲があったときに皆さんはどうしてましたか? DJ7ではいちいちリクエストして流してもらうという手間はいりません。動画を共有する
なにか面白い動画があったときにURLで共有することがあると思います。DJ7ではURLを共有する手間も、URLをクリックして開いてもらう手間も必要ありません。
動画を流してリモートで授業
これはやったことがありませんが、可能だと思います。同じ動画をみて、重要な点は一時停止して解説をするといったことができます。
ほかにもこんな使い方はどうですか?
- オンラインでカラオケ
- お笑いのライブを一緒にみて笑う
- ライブコーディングの同時視聴
- 製品発表や商品レビューをみんなで思ったことを言いながら視聴
- 勉強会
私が知らないだけで使い方はもっとたくさんあると思います。ぜひみなさん思い思いの使い方をしてみてください!!
開発スケジュール
今回の開発スケジュールは以下のような感じです。
工程 工数 デモ作成 3日程度 ロジック実装 10日程度 デザイン 10日程度 その他 7日程度 最初の3日で同時視聴ができるデモを簡単に作り、知り合いに見せました。このときポジティブな感想をもらえたのがモチベーションになりました。
そこから仲間内で実際に1ユーザとして使ってもらい意見をもらいながら開発を進めました。
また、1ヶ月で作るということを最初に決めていましたので、機能の実装は最低限必要なものだけに絞り、機能を追加するよりは、すでにある機能のブラッシュアップを優先させること常に意識していました。次回は今回学んだことを活かして、もっと早くサービスのローンチまで漕ぎ着けたいです。
技術スタック
今回はフロントエンドにVue、バックエンドにFirebaseを使いました。
とくに同期再生といった根幹の部分はFirestoreだけで実装されています。
Cloud Functionsなども使いませんでしたので、コーディングはフロントエンドだけに集中できました。Firebase最高。
苦労した点
モバイルについてです。AndroidやiOSでは、OS側の制約により、YouTubeの音をミュートにしなければサイトを訪れたときに自動で再生されません。そのため、サイトを訪れてから最初にYouTubeのプレイヤーを操作していただきミュートを解除していただく必要があります。この点だけご了承していただきたく・・・
また、今回の場合ではあまり難しいロジックはありませんでしたので、実装自体にはあまり苦労しませんでした。
まとめ
今回は公開のためにあまり機能をつけませんでしたが、私自身欲しい機能がまだまだたくさんあるのでバージョンアップを重ねていきたいと思います。
技術についての詳しい話や、サービスの途中経過も適時報告したいと思います。質問はTwitter @imataka7 または @dj7app までお願いします!
また、こちらのDiscordのサーバでWebサービスを個人開発する人たちで切磋琢磨するコミュニティを運営しています!
Dj7のリリースに至るまでこのコミュニティで沢山のフィードバックを貰えたのはかなり大きかったです。
Webサービスをこれから作りたいという初心者でも大歓迎なので気軽に参加してみて下さい!
https://discord.gg/hNWjDBdYouTube同期再生プラットフォーム DJ7 https://www.dj7.io