- 投稿日:2020-04-02T23:30:40+09:00
【カンタン】Vueで縦に自動伸縮するtextareaの作り方 | How to make auto resizing <textarea> by Vue.js
インターネッツで調べてみても中途半端なtextareaしか出てこなかったので、備忘も兼ねた記事です。
伸びるけど縮まないとか、縮むけど1文字いれるごとに2pxずつしか縮まないとか、意味なくない!?!?!?!?!?!?
んおお!?!?!?!?
まずは結果
うおおおおおおおお
うおおおおおおおお
うおおおおおおおお
うおおおおおおおお
うおおおおおおおおうおおおおおおおお
うおおおおおおおお
うおおおおおおおおうおおおおおおおおうおおおおおおおお
うおおおおおおおお
うおおおおおおおお
うおおおおおおおおコード
今気づいたけどマークダウンの候補にvuejsってある。。
便利すぎない?業務で書いたコードからいらないやつ消した感じなので、このまま動かなかったらごめんなさい。
編集リクエスト送っていただければーーーMyTextarea.vue<template> <textarea class="textarea bg-white" :style="textareaStyle" :placeholder="placeholder" :value="value" @input="handleInput($event)" /> </template> <script lang="ts"> import Vue from "vue" export default Vue.extend({ props: { placeholder: { type: String, default: "" }, value: { type: String, default: '' } }, data() { return { textareaHeight: 100 // デフォルト値いれとく。minHeightといっしょでよい。borderあるのでちょっとずれる } }, computed: { textareaStyle(): object { // 動的にtextareaのheightをいじれるようにしている return { height: `${this.textareaHeight}px` } } }, methods: { async handleInput(event: any) { // 入力されるたびによばれる。anyなのはゆるして。。。 this.$emit('input', event.target.value) // これは親に伝えるためだけ。 this.textareaHeight = 0 // ミソ。一旦textareaのheightを0にしちゃう await this.$nextTick() // さらにミソ。ここで待たないとDOMの更新前に下のコードが走って変な挙動になる // heightが0になった瞬間textareaはminHeight: 100になる // 入力済み文字列の高さが100を超えたら、scrollHeightが必要な分だけ大きくなる = それをheightにしちゃえばOK! this.textareaHeight = event.target.scrollHeight } } }) </script> <style lang="stylus" scoped> .textarea-container { width: 100%; } .textarea { width: 100%; min-height: 100px; // ここはお好み。変えるならdata()の値も変えるとよいよ border: 1px solid #D9D9D9 border-radius: 4px; padding: 5px 12px; &::placeholder { color: #D9D9D9 } } </style>TL;DR
this.$nextTickがミソなんじゃぞ。
- 投稿日:2020-04-02T22:44:16+09:00
Vue.js でインクリメンタルサーチ
インクリメンタルサーチとは
インクリメンタルサーチ(英語: incremental search)は、アプリケーションにおける検索方法のひとつ。検索したい単語をすべて入力した上で検索するのではなく、入力のたびごとに即座に候補を表示させる
完成品
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>インクリメンタルサーチ</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <link rel="stylesheet" href="style.css"> </head> <body> <div id="app"> <!-- v-model を指定する --> <input type="text" placeholder="検索" v-model="search"> <!-- sort とソート順選択欄を v-modelで結ぶ --> <select v-model="sort"> <option value="">ソート無し</option> <option value="asc">昇順</option> <option value="desc">降順</option> </select> <!-- v-for を使ったリストをアニメーションさせるためのコンポーネント <transition-group>key 属性を付ける必要がある --> <transition-group tag="ul"> <!-- <li v-for="item in list"> listプロパティの値をv-forで回す --> <!-- fileterdListを表示 --> <li v-for="item in sortedList" v-bind:key="item.id"> {{ item.text }} </li> </transition-group> </div> <script src="main.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </body> </html>JS
new Vue({ el: "#app", data:{ //入力フォームに入力された値を表すデータを定義 search: '', //現在どの並び順でソートしているのかを表すデータ sort: '', //、次は検索対象のデータを定義 list: [ { id: 1, text: 'Python' }, { id:2, text: 'Ruby'}, { id:3, text: 'PHP'}, { id:4, text: 'JavaScript'}, { id:5, text: 'Java'}, { id:6, text: 'Go'}, { id:7, text: 'C'}, { id:8, text: 'C#'}, { id: 9, text: 'Rails' }, { id:10, text: 'Django'}, { id:11, text: 'MySQL'}, { id:12, text: 'Vue.jst'}, { id:13, text: 'react'}, { id:14, text: 'Docker'}, { id:15, text: 'unity'}, { id:16, text: 'jQuery'} ] }, computed: { //検索処理としてfileterdListを定義 filteredList: function(){ //filter のコールバック関数内では引数に配列の各項目が渡され、返り値が true となった項目のみ出力 return this.list.filter(function(item){ //ある文字列に他の文字列が含まれているかどうかを調べる時は indexOfを使用 //item.text 文字列に、this.search という文字列が含まれている時に、その文字列の場所を表す数値を返し、含まれていないときには -1 を返し return item.text.indexOf(this.search) > -1 }, this) }, //ソート後のデータを返す算出プロパティ sortedList sortedList: function(){ var copy = this.filteredList.slice() if(this.sort === 'asc' ){ //comparatorAsc メソッドをコールバックとして sort 関数に渡す return copy.sort(this.comparatorAsc) } else if(this.sort === 'desc') { //降順に並べ替えるためのコールバック関数は comparatorDesc return copy.sort(this.comparatorDesc) } else{ return copy } } }, methods:{ comparatorAsc: function(itemA, itemB){ if(itemA.text < itemB.text){ return -1 } else if(itemA > itemB.text){ return 1 } else{ return 0 } }, comparatorDesc: function(){ if(itemA.text > itemB.text){ return -1 } else if(itemA < itemB.text){ return 1 } else{ return 0 } }, } });CSS
body{ font-family: sans-serif; } input, select{ padding: 2px 8px; font-size: inherit; vertical-align: middle; } ul { position: relative; margin-top: 6px; padding: 0; width: 300px; list-style: none; } li{ margin: 0; padding: 10px; border-bottom: 1px solid #ddd; } .v-move{ transition: transform 300ms ease-out; } .v-enter-active { transition: 300ms; } .v-enter{ opacity: 0; } .v-enter-to{ opacity: 1; } .v-leave-active{ transition: 300ms; } .v-leave{ opacity: 1; } .v-leave-to{ opacity: 0; }
- 投稿日:2020-04-02T18:41:59+09:00
ハンバーガーメニューの作成
HTML5
<body> <!-- ハンバーガーボタン --> <div class="ham" id="ham"> <!-- ハンバーガーボタンのライン --> <span class="ham_line ham_line1"></span> <span class="ham_line ham_line2"></span> <span class="ham_line ham_line3"></span> </div> <!-- メニューの中身 --> <div class="menu_wrapper" id="menu_wrapper"> <div class="menu"> <ul> <li><a href="#1" style="color: #FFF;">メニュー1</a></li> <li><a href="#2" style="color: #FFF;">メニュー2</a></li> <li><a href="#3" style="color: #FFF;">メニュー3</a></li> </ul> </div> </div>CSS
.ham{ position: relative; width: 40px; height: 40px; cursor: pointer; background-color: rgba(0, 0, 255, 0.911); } /* ハンバーガボタンのライン */ .ham_line { transition: all 0.3s; position: absolute; left: 10px; width: 20px; height: 2px; background-color: #FFF; } .ham_line1 { top: 10px; } .ham_line2 { top: 18px; } .ham_line3 { top: 26px; } /* Javascriptでクリックイベントが発火した後の処理 ハンバーガボタンのライン */ .clicked .ham_line1{ transform: rotate(45deg); top: 20px; } .clicked .ham_line2{ width: 0px; } .clicked .ham_line3{ transform: rotate(-45deg); top: 20px; } /* メニューの表示 */ .menu { position: fixed; left: -400px; width: 300px; height: 300px; background-color: rgba(0, 0, 255, 0.849); transition: all 0.5s; color: #FFF; } .clicked .menu { left: 8px; }JavaScript
const ham = document.querySelector('#ham'); const menu_wrapper = document.querySelector('#menu_wrapper') // addEventListener さまざまなイベント処理を実行することができるメソッド ham.addEventListener('click', function(){ // classListとは、対象要素に設定しているクラスを配列のように扱えるオブジェクト ham.classList.toggle('clicked'); menu_wrapper.classList.toggle('clicked') });addEventListener
classListとは
- 投稿日:2020-04-02T18:21:06+09:00
写真の右上に三角形のラベルをつける
1 写真の方にラベルをつける方法
今回は、下の写真のような、imageの肩に斜めの装飾をつける方法を学んだので共有したいと思います。
2 必要な記述
今回は簡単なサンプルとして要素を書いています。
<hamlの記述>
.image-box = image_tag @image.image .triangle-box sold /*sold outの意味*/hamlの記述は以上です。
ポイントは装飾したい写真を表示させる記述のしたに、ボックスを追加することです。
細かいことは、cssでいじっていきます。<scssの記述>
.image-box { width: 100px; /*適当にセットしてください*/ height: 100px; /*適当にセットしてください*/ position: relative; overflow-x: hidden; ←ポイント① = image_tag @image.image .triangle-box{ position: absolute; /* 必須(絶対配置にするため) */ top: -15px; /* 掲載位置の調整(座標) */ left: -130px; /* 掲載位置の調整(座標) */ background-color: rgba(255,0,0,0.4); /* 背景色の調整(透けるように調整) */ padding: 80px 40px 0 40px; /* サイズ調整:配置 */ width: 140px; /* サイズ調整:横幅 */ height: 120px; /* サイズ調整:高さ */ font-size: 1em; /* サイズ調整:文字 */ font-weight: bold; font-size: 50px; /* サイズ調整:行高 */ transform: rotate(-45deg); ←ポイント② /* 傾きの度合い */ transform-origin:top; text-align: center; } }ポイントは二つ
① はみ出した部分が表示されないようにする
② 写真の肩に乗せたい要素を斜めにするoverflow-x: hidden;これがないと、要素が表示されます。
(今回はうまく表示させることができませんでした。検証ツールで[overflow-x: hidden;]を非適用にしても効きませんでした。)移動させる要素(.triangle-box)の親要素(.image-box)に記述してください。
あとはきれいに見えるように微調整を繰り返してください。
見栄えがよくできる割りに作業は簡単なのでぜひぜひ試してみてください。
最後まで見てださり、ありがとうございました。
参考記事
https://www.nishishi.com/css/round-caption-img.html
https://haniwaman.com/background-skew/
- 投稿日:2020-04-02T10:05:33+09:00
ライブ配信者向けレイアウト構築システム「StreamControl」の使い方
StreamControlの概要
何が出来るの
(画像は第2回SplashGo!より)
ゲーム大会などでよく見るこういう画面レイアウトを作れるようになります。強み
一般の配信ソフトでテキストを直書きすると……
- テキストを書き換えるまでに時間がかかる
- 入力ミスが起こりやすい
- 入力内容を用意した別資料を見ながら入力しなければならない
StreamControlを使えば……
- 瞬時にテキストを切り替えることが可能
- 上記画像だと、チーム名+選手名4人という合計5要素を瞬時に切り替えることが可能です。
- 切替可能な要素数は設定により可変です。例えば、各選手のツイッターアカウントも出したいといった要件も可能です。
- 切り替え対象のIDを選ぶだけなので入力ミスが起こりづらい
- 上記画像だと、それぞれのチームIDを選ぶだけで切り替えが可能というイメージです。
- 入力内容は事前に用意したものを参照するので、別資料が必要ない
また、後述するようにhtml&css&javascriptによって構築されています。
従ってこれらの技術で実現可能なことなら、何でも自由にレイアウトを組むことが出来るという点も強みかと思います。仕組み
配信画面はhtmlでできている
こんな感じで、配信レイアウトにしたい画面をhtmlで構成します。
最低限チーム名の部分などをdivで指定するだけでできます。
チーム名などのテキスト部分はjavascriptを動かして取得・表示します。
htmlとは別のxmlファイルに書かれている内容を取得しています。
xmlファイルの中身はシンプルにこんな感じです。
となると、チーム名などを切り替えるためにはxmlファイルの中身を書き換えればいいということになります。
ここでようやくStreamControlが登場します。StreamControlと連携させる
StreamControl側の画面はこうなっています。
ここで保存ボタンをクリックすると、StreamControlに入力している内容を元に先述のxmlファイルが上書きされるという仕組みです。
チームIDは前方一致で候補を出してくれるので選びやすくなっています。実際に選んでいる様子を動画で確認してみて下さい。
動画だと候補が出ている部分が映りませんでしたが、ラクに切り替え出来ているのが分かるかと思います。
データソースはこのようなcsvファイルになっています。
1列目のIDを指定することで、その行に対応した各列の値をセットするイメージです。配信ソフトでhtmlを読み込む
ここまで出来れば、あとは各種配信ソフトでhtmlファイルを読み込むだけです。
レイアウト上の情報を更新したくなったら、StreamControlを操作して保存ボタンを押すだけです。
配信ソフト側は何もいじる必要はありません。更新されたhtmlを自動で読み込み直してくれます。実際に作ってみよう
ダウンロード
配布元はこちらです。
http://farpnut.net/streamcontrol/ですが、有志の方が公開している、既に組み上がっているレイアウトを手直しするのも良いでしょう。
各種レイアウトは日本 StreamControl 学会で公開されています。
その他、StreamControlでググることでも各種レイアウトを探すことが出来るでしょう。2020/4/1追記
レイアウト公開の許可を頂いたので、本記事で使用しているものをGitHubに公開しました。
https://github.com/anaakikutushita/64GB_Layout設定
StreamControlのレイアウト
StreamControlの画面上にテキストボックスやボタンを置いていきましょう。
設定画面を開き、Layout File
でxmlファイルを指定します。
このxmlファイルに書かれた内容の通りにテキストボックスなどが配置されます。
layout
タグで全体を囲む- 必要な数だけ
tab
タグを作るtab
タグの中にlabel
やlineEdit
(テキストボックス)を配置する
lineEdit
のid
属性と、html側のid
を揃えると良さげですdataSet
属性にcsvファイルを指定する- あとはいい感じに
dataField
属性やmaster
属性を設定仕様はドキュメントで確認できます。
http://farpnut.net/streamcontrol/layout-documentation/いい感じに出来たらcsvを作って動作確認してみてください。
出力するxmlファイルはstreamcontrol.xml
となります。javascriptの設定
html及びcssはいい感じに作ってください。
xmlから要素を取得してhtml内に反映させるところを解説します。
基本的な構造は次のようになります。
init()
の中にやりたいことを全部書く
- xmlファイルの変更を検知するため、setIntervalで定期的に読みに行く
body
タグのonload
属性にinit()
を指定する全部載せると煩雑になるので、主要な部分を載せます。
var xmlDoc; var xhr = new XMLHttpRequest(); var doUpdate = false; function init() { xhr.overrideMimeType('text/xml'); var timeout = this.window.setInterval(function() { pollHandler(); }, 250); $('#round').html(''); ... //その他初期化 $.play(); } function pollHandler() { loadData(); // htmlの中身を更新すべきかどうか、loadDataしたときのtimestampを使って判断します if (timestamp != timestampOld) { doUpdate = true; } if (doUpdate) { updateBoard(); } } function loadData() { xhr.open('GET', 'streamcontrol.xml'); xhr.send(); xhr.onreadystatechange = function(){ xmlDoc = xhr.responseXML; round = getValueFromTag(xmlDoc,'round') // その他、タグを指定してxmlから値を取得 timestampOld = timestamp; timestamp = getValueFromTag(xmlDoc,'timestamp'); } } function getValueFromTag (xmlDoc,tag) { if (xmlDoc.getElementsByTagName(tag).length != 0 ) { if (xmlDoc.getElementsByTagName(tag)[0].childNodes.length == 0) { return ''; } else { return xmlDoc.getElementsByTagName(tag)[0].childNodes[0].nodeValue; } } else { return ''; } } function updateBoard() { // xmlから取得した値でhtmlの各要素を上書き $('#round').html(round) ... $.play(); doUpdate = false; }<body onload="init()"> ... </body>javascriptが動けば、StreamControlが出力するxmlと連携してレイアウトの更新が出来るようになると思います。
フロントエンドメッチャデキル人が何かスゴいレイアウトを作ってくれたらいいと思います。まとめ
システム模式図
StreamControlのシステム全体を模式図にするとこんな感じになるかと思います。
用意するものが少し多くて大変ですが、使いこなせれば配信する上で非常に便利です。
お読み頂きありがとうございました。分からないところなどあればコメント頂けると幸いです。この記事は随時更新してより良いものに仕上げていきたく思います。
参考にしたのとか
先駆者の方々のお力添えによって少しでもStreamControlを使えるようになりました。ありがとうございます。
StreamControl テンプレート 2019年版 配布
http://shigaming.com/2018/11/30/streamcontroltemplate2019/OBSでのStream Control テンプレートの使い方(SHIG 仕様2019版)
https://pick-up-space.com/streamcontrol01/ソフトの存在および基本的な使い方はがまのあぶらさんに教わりました。
https://twitter.com/gamanoabura_nsbちなみに:上記SHIGのレイアウトでは国旗を表示させることも出来ます。
こちらはhtmlに表示したいテキストをxmlに出力したのと同様に、表示したい国旗画像ファイルのファイル名をxmlに出力してあげてjavascript側でimgタグのsrc属性を書き換えてあげればできます。
国旗画像のアセットはネット上を検索すれば見つけられます。
- 投稿日:2020-04-02T00:25:41+09:00
画像をトリミングできる object fitプロパティが使える件
『HTMLで表示する画像のサイズを調節したい! でも、縦横の比率はそのままにしたい!』
というケースは多いと思います。
そんな時はobject-fitプロパティを使いましょう。元の画像がこんな感じだとします。
この画像を縦300px横500pxで埋め込むとします。
img { height: 300px; width: 500px; }サイズは合いましたが、比率は歪んでしまっています。
早速ここでobject-fitを使ってみます。img { height: 300px; width: 300px; object-fit: cover }比率が維持されつつ、枠に収まっています。
今回はカバーを指定しましたが、他にも,
fill, contain, none, scale-downなんかがあります。見てもらったほうが早いんじゃないかと個人的には思います。
fill
文字通り画像がはめ込まれています。
img { height: 300px; width: 500px; object-fit: fill; }contain
元の画像の縦横の比率を維持したまま、左右に余白が生じています。
img {
height: 300px;
width: 500px;
object-fit: contain;
}
none
指定サイズとは関係なく画像が表示され、残りはトリミングされています。
img { height: 300px; width: 500px; object-fit: none; }scale-down
画像が指定より大きい場合はcontain,小さい場合はnoneが適用されます。
この場合はnoneですねimg { height: 300px; width: 500px; object-fit: scale-down; }今回はobject-noneについてまとめてみました。
まだまだQiitaに慣れず、ブログに結構時間が掛かってしまいます、、、(そもそもブログというものを書く事自体が2回目)今度はもう少しペースをあげて、アウトプットの効率を高めていきたいものです。