20210416のCSSに関する記事は9件です。

中学1年生がCADソフトを自作した話

noteで書きました→ https://note.com/ittakun/n/nd529757c6ff1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

回転する見た目も実装、jQueryで作るコイントスシステム

どうも7noteです。コインが回転するコイントスシステムを作った コイントスで調べると、表と裏がランダムで表示されるレベルのコイントスシステムはあったのですが、実際にコインが空中で回転するような見た目のコイントスはなかったので作ってみました。 コイントスシステムのソース index.html <div class="coin"></div> style.css .coin { width: 50px; /* コインの幅 */ height: 50px; /* コインの高さ */ background: url(omote.png) no-repeat center / 100% auto; /* 背景にコインの画像を設定 */ position: relative; /* 表示位置を変更するために必要 */ cursor: pointer; /* hover時にカーソルを指の形にする */ } .coin.toss { animation : toss 0.3s linear infinite,toss2 0.3s infinite steps(1, end),updown 2s ease; /* anime「toss」、anime「toss2」、anime「updown」 */ } /* X軸を基準に回転させるアニメーション */ @keyframes toss{ 0% { transform:rotateX(0deg); } 100% { transform:rotateX(360deg); } } /* 表裏を切り替えるアニメーション */ @keyframes toss2{ 25% { background-image: url(ura.png); } 75% { background-image: url(omote.png); } } /* コインを上下させるアニメーション */ @keyframes updown{ 0% { top: 0; } 50% { top: -100px; } 100% { top: 0; } } script.js $(function(){ $('.coin').not(':animated').on('click',function(){ /* ランダムで0か1を取得 */ rndm = Math.floor( Math.random() * 2 ); /* アニメーションを開始させる */ $(this).addClass('toss'); /* 1.9秒後にアニメーションを停止し、コインの裏表を表示する */ setTimeout(function(){ $('.coin').removeClass('toss'); if(rndm == 1){ $('.coin').css(`background-image`,`url(ura.png)`); } else { $('.coin').css(`background-image`,`url(omote.png)`); } },1900); }); }); ※jQueryを使用しています。jQueryについてはこちら 解説 ・3種類のcssアニメーション ・jsでの表裏のランダム、アニメーションの起動 大きくこの2つに分けて解説します。 ▼ 3種類のcssアニメーション アニメーションは3つのアニメーションをカンマ区切りで書いて1行にしているだけですのでこれも分けて解説していきます。 「toss」 .coin { animation : toss 0.3s linear infinite; /* 0.3秒ごとにアニメーションを繰り返す */ } /* X軸を基準に回転させるアニメーション */ @keyframes toss{ 0% { transform:rotateX(0deg); } 100% { transform:rotateX(360deg); } } X軸、つまり コインを(ー)←この向きを軸として回転をさせるアニメーションです。 「toss2」 .coin { animation : toss2 0.3s infinite steps(1, end); /* 0.3秒ごとにアニメーションを繰り返す。かつ徐々にではなくコマ割りで動かす。 */ } /* 表裏を切り替えるアニメーション */ @keyframes toss2{ 25% { background-image: url(ura.png); } 75% { background-image: url(omote.png); } } 表のコインを回転させるだけでは、コインが回転してもずっと表の絵が見えてしまうので、コインが反転したタイミング、つまりコインが回転する際に横一線になる瞬間の25%時間経過したタイミングで裏に、75%時間経過したタイミングでまた表に戻すことで表裏のあるコインが回転しているように見せることができます。 通常backgroundはアニメーションを指定すると徐々に変化してしまうので、表の文字がどんどん薄くなる・・・みたいなことになってしまうのでそうならないようにsteps(1, end)を指定することで、コマ撮りのような瞬時に切り替わる設定を入れています。 「updown」 .coin { animation : updown 2s ease; /* 2秒かけて1回アニメーションする */ } /* コインを上下させるアニメーション */ @keyframes updown{ 0% { top: 0; } 50% { top: -100px; } 100% { top: 0; } } topの指定を変更することで、コインを空中に飛ばすようなアニメーションになります。 ▼ jsでの表裏のランダム、アニメーションの起動 jsに関してはほぼコメントを入れている通りの処理を順番に行っているだけです。 $(function(){ $('.coin').not(':animated').on('click',function(){ /* ランダムで0か1を取得 */ rndm = Math.floor( Math.random() * 2 ); /* アニメーションを開始させる */ $(this).addClass('toss'); /* 1.9秒後にアニメーションを停止し、コインの裏表を表示する */ setTimeout(function(){ $('.coin').removeClass('toss'); if(rndm == 1){ $('.coin').css(`background-image`,`url(ura.png)`); } else { $('.coin').css(`background-image`,`url(omote.png)`); } },1900); }); }); コインがクリックされた時 ↓ ランダムで0か1を決定、かつtossアニメーションを開始させるためにクラス「toss」を付与 ↓ 1.9秒後にアニメーションを停止させるためにクラス「toss」を取り除く。かつ0か1の値を取得してそれに合わせて表示する画像を決定。 こんな感じの流れになりますね。 まとめ 本当はcssだけで作りたかったのですがランダムを再現しようと思うとさすがに骨が折れそうだったので、js便りになってしまいました。 cssマスターならきっとランダムっぽい再現もできるのかもしれませんがまだまだ勉強不足を感じます。 私的にはきれいにアニメーションを作ることができたので、webのオンラインゲーム等を作成するときにはぜひ使ってみたいですね。 jsを追加して、これまで出た表か裏かを画面端に記録していくようなシステムなんかも追加してもいいかもしれませんね。 画像データ:いらすとやよりお借りしました。 https://www.irasutoya.com/2017/12/blog-post_410.html おそまつ! ~ Qiitaで毎日投稿中!! ~ 【初心者向け】WEB制作のちょいテク詰め合わせ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSSだけ使って、どんな画面サイズでも縦横比と余白を維持したいんじゃ

やりたいこと 画面サイズによってメインコンテンツの大きさを変えたいけど縦横比は維持したい。 メインコンテンツは画面から少しマージンを取る。画面からはみ出さないようにする。 JavaScript で画面のサイズを計算してやってもいいけど動きがモッサリだから CSS だけでやりたい。 やってみた 縦と横の比は 4:3 画面サイズの 95% を超えないようにする この条件で実装してみました。 実装方法 min と vh vw を組み合わせたらスッキリ書けました。実質2行。 <div class="content"> <div class="box"></div> </div> /* メインコンテンツ */ .box { background: #eee; width: min(95vw, 95vh * 0.75); /* 横幅の計算 */ height: min(95vh, 95vw / 0.75); /* 縦幅の計算 */ } /* ※中央寄せしたいだけなので、ここから先は本題と関係ない */ html,body, .content { width: 100%; height: 100%; background: #263238; } .content { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; } 説明 場合わけして考えてみます。 まずタテに長い画面の場合は・・・こうなってほしいですよね。 次にヨコに長い画面の場合は・・・こう。 min 関数を使うと、引数のうち小さい方を採用してくれるので、画面幅の95%を超えないように抑えてくれます。 .box { background: #eee; width: min(95vw, 95vh * 0.75); height: min(95vh, 95vw / 0.75); } 95% ではなく、かならず画面端から 20px 離したいというときはこう書きます。 .box { background: #eee; width: min(100vw - 20px, (100vh - 20px) * 0.75); height: min(100vh - 20px, (100vw - 20px) / 0.75); } 補足 メディアクエリでタテ長の場合とヨコ長の場合で分けて書いても構いません。 (こっちのほうがわかりやすいですね) /* 縦長の場合 */ @media screen and (orientation: portrait) { .box { width: 95vw; height: calc(95vw / 0.75); } } /* 横長の場合 */ @media screen and (orientation: landscape) { .box { width: calc(95vh * 0.75); height: 95vh; } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSSについて

CSSでタグを指定することがあると思います。 例えば、li{ } みたいな感じです。しかし、これではファイル内のすべてのにcssが適応されてしまいますので、 .class名 li { } とすると該当するclass名のにのみ適応されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでSlackみたいなメンションと入力中の名前色付けを作った完成形コードとハマった話と解決法

Repsona LLCの@GussieTechです。「理想のプロジェクト管理ツール」Repsonaを開発しています。 RepsonaではSlackとかTeamsみたいにメンションでお知らせできるようになっています。受信トレイの新規開発に合わせて、メンションの体感もカイゼンしようということで立ち向かったので、その完成形コードと、そこに至るまでのハマりポイントや解決法などをまとめておきます。色付きメンションの記事は案外ないのでお役に立てれば嬉しいです。 完成形 忙しい人へ 完成形のコード抜粋をCodeSandboxにつくりました。 環境 Vue v2.x + Bootstrap v4.x + vue-mention ハマった話と解決法 もくじ メンションポップアップ Tributeが期待した位置に表示されてくれない vue-mentionが表示されない vue-mentionの表示位置がおかしい 全角@で期待通りに動いてくれない 入力中の名前色付け textarea内の文字に色をつける方法がない スクロールバーの有無で色付け位置がズレる メンションポップアップ Tributeが期待した位置に表示されてくれない リリース当初よりTributeでメンションの動作を実装していました。通常の文字数や表示位置では問題なく動作していたのですが、画面全体のスクロール位置や入力文字数によって期待した位置に表示されない問題がありました。かなり試行錯誤をしたのですがうまくいかなかったため、vue-mentionに乗り換えることにしました。 vue-mentionが表示されない 乗り換えたところ、なぜか表示されませんでした。これがかなりハマりました。vue-mentionはv-tooltipに依存しているのですが、Bootstrapの.tooltipクラスとぶつかって、なんとopacity: 0;が採用されてしまっていました。 v-tooltip内の.tooltipに関しては、opacity: 1;が適用されるようにCSSを設定して回避しました。 [id^="popover_"].tooltip { opacity: 1; } vue-mentionの表示位置がおかしい いや違う、左側ではなく右側にでて欲しいのです。デモでは右に出ているのです。 細かい原因は調べませんでしたが、きっとまたBootstrapのCSSと競合しているのだろうと思いました。そこでまるっとv-tooltipのCSSが優先して適用されれば良いのではないかと考えました。 CSSは、対象を詳しく指定しているセレクタが優先されるので、idと合わせて指定することでオーバーライドしてやります([id^="popover_"].tooltip)。v-tooltipのデフォルトのCSSで上書きすることで、うまく解決しました。 [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 24px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, .1); } .popover-arrow { border-color: $color; } } &[aria-hidden='true'] { visibility: hidden; opacity: 0; transition: opacity .15s, visibility .15s; } &[aria-hidden='false'] { visibility: visible; opacity: 1; transition: opacity .15s; } } きました!(幅やカラー指定の調整は省略しています) 全角@で期待通りに動いてくれない 全角@でポップアップが出て、確定するとなんだか@がいっぱい出てきてしまいます。 ドキュメントに記載はないのですが、どうやら隠しオプションみたいなものがあって、omitKeyというのを見つけました。 これをセットすることで、起動キーを排除した上で、valueをセットできるようになりました。valueには@も含めることで、期待通り動作します。でも、まだもうひとつ@が残ってしまいます。 IMEの変換確定とメンションの決定が重なって、決定後に@が残ってしまっています。IME確定後のみ決定を発動させる必要がありそうです。 vue-mentionのコードをみてみるとonKeyDown()が担っているようです。この部分ですね。 if ((e.key === 'Enter' || e.key === 'Tab' || e.keyCode === 13 || e.keyCode === 9) && 非常にdirtyですがe.key === 'Enter' || e.key === 'Tab'を無効にするオーバーライドをすることで回避しました。しかし、keyCodeはdeprecatedなので、いずれ期待通り動かなくなるかもしれません。他サービスで全角@が気持ちよく動作しないものがあるのは、きっとこの辺りの事情があるのでしょう。英語圏の人たちには関係ないでしょうし・・。ところで、正しくはどう実装するべきなのだろう。 入力中の名前色付け textarea内の文字に色をつける方法がない さまざまなサービスで簡単そうに実現しているのですが、甘くみていました。textarea css font-colorなどとググってみても、どうやらtextarea内の特定の文字に色をつける方法はないようです。 そうするとcontenteditableが頭をよぎりますが、プレーンテキストとして取り扱うのはかなりしんどさがあります(以前とんでもなくハマったのですがここでは省略します)。 そこで、textareaに、入力中の文字とまったく同じ文字を表示するdivを重ねる方法を採用しました。 ピンクで表示したdiv内にtextareaで記述した内容をそのまま透明文字で表示して、必要に応じて<span>等でカラーをつける作戦です。仮実装してすぐ期待通りに動いたので、簡単そうに見えました。しかし・・・ スクロールバーの有無で色付け位置がズレる ああ・・なるほど。スクロールバーさんを忘れていました。サイズ変更などでも表示されますね。スクロールバー表示有無を検出してスクロールバー幅分をpaddingしてやる必要がありそうです。 const barWidth = this.$refs.comment.$el.getBoundingClientRect().width - this.$refs.comment.$el.clientWidth (実際はtextareaに適用されたボーダーなどのスタイルも加味して計算しています) うまくいきました。 完成形 <template> <div id="app"> <div class="p-3"> <div class="position-relative"> <div ref="textareaCover" class="textarea-cover" v-html="commentMention" ></div> <mentionable :keys="['@', '@']" :items="users" insert-space omit-key> <textarea ref="comment" class="form-control comment" rows="4" v-model="comment" @keyup="commentScroll" @scroll="commentScroll" /> <template #item="{ item }"> <div class="user"> <span class="font-weight-bold"> {{ item.value }} </span> <span class="ml-2"> {{ item.firstName }} </span> </div> </template> </mentionable> </div> </div> </div> </template> <script> import { Mentionable } from "vue-mention"; Mentionable.methods.onKeyDown = function (e) { if (this.key) { if (e.key === "ArrowDown" || e.keyCode === 40) { this.selectedIndex++; if (this.selectedIndex >= this.displayedItems.length) { this.selectedIndex = 0; } this.cancelEvent(e); } if (e.key === "ArrowUp" || e.keyCode === 38) { this.selectedIndex--; if (this.selectedIndex < 0) { this.selectedIndex = this.displayedItems.length - 1; } this.cancelEvent(e); } if ( (e.keyCode === 13 || e.keyCode === 9) && this.displayedItems.length > 0 ) { this.applyMention(this.selectedIndex); this.cancelEvent(e); } if (e.key === "Escape" || e.keyCode === 27) { this.closeMenu(); this.cancelEvent(e); } } }; export default { name: "App", components: { Mentionable, }, data() { return { comment: "", users: [ { value: "@akryum", firstName: "Guillaume", }, { value: "@posva", firstName: "Eduardo", }, { value: "@atinux", firstName: "Sébastien", }, ], }; }, computed: { commentMention() { if (typeof this.comment?.replaceAll !== "function") { return this.comment; } const replaced = this.comment ?.replaceAll("&", "&amp;") ?.replaceAll(">", "&gt;") ?.replaceAll("<", "&lt;") + "\n\n"; const search = new RegExp( this.users .slice() .sort((a, b) => b.value?.length - a.value?.length) .map((user) => user.value) .join("|"), "g" ); return replaced.replace(search, (match, offset) => { return `<span class="mention-str">${match}</span>`; }); }, }, methods: { mounted() { setTimeout(() => { this.resize(); window.addEventListener("resize", this.resize); this.$once("hook:beforeDestroy", () => { window.removeEventListener("resize", this.resize); }); }); }, resize() { const barWidth = this.$refs.comment.getBoundingClientRect().width - this.$refs.comment.clientWidth - 2; // border this.$refs.textareaCover.style.paddingRight = `calc(12px + ${barWidth}px)`; }, commentScroll() { this.$refs.textareaCover.scrollTop = this.$refs.comment.scrollTop; this.resize(); }, }, }; </script> [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 10px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, 0.1); } .popover-arrow { border-color: $color; } } &[aria-hidden="true"] { visibility: hidden; opacity: 0; transition: opacity 0.15s, visibility 0.15s; } &[aria-hidden="false"] { visibility: visible; opacity: 1; transition: opacity 0.15s; } } .mention-item { padding: 4px 10px; border-radius: 4px; } .mention-selected { background: #00ABE7; color: white; } .textarea-cover { z-index: 1; position: absolute; color: transparent; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: hidden; padding: 6px 12px; border: 1px solid transparent; } .comment { width: 100%; } .mention-str { color: #00ABE7; background-color: rgba(0, 171, 231, 0.05); } 実際のサービス上は血の滲むようなCSSの調整をがんばっております。もしかしたらまだバグがあるかもしれません。何かお気づきの点がありましたらお知らせいただけますと幸いでございます。 まとめ vue-mention + Bootstrap は css をオーバーライド(リセット)して使う 全角@問題はkeyCodeで解決する(ただしdepricatedなのが悩ましい) textareaに色付けできないので上に文字を重ねて表現する 簡単そうで実は難しい「普通の動き」の実装にとっても苦労してハマりました。これで気持ちよくメンションして受信トレイでサラサラと通知を管理できるようになりそうです。ますます便利になった「プロジェクト管理と情報共有のためのツール(ガントチャート無料)」Repsona、ぜひお試しください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでSlackみたいなメンションと入力中の名前色付けを作った完成形コードとハマった話

Repsona LLCの@GussieTechです。「理想のプロジェクト管理ツール」Repsonaを開発しています。 作ったもの これを作るのに大変苦労しました。textareaの特定文字に色はつけられません。ではどうするか?そんなお話です。 忙しい人へ 完成形のコード抜粋をCodeSandboxにつくりましたのでご利用ください。でも・・僕の苦労話も是非読んでいってください・・ 環境 Vue v2.x + Bootstrap v4.x + vue-mention ハマった話と解決法 RepsonaではSlackみたいにメンションでお知らせできるようになっています。受信トレイの新規開発に合わせて、メンションの体感もカイゼンしようということで立ち向かいました。色付きメンションの記事は案外ないのでお役に立てれば嬉しいです。 もくじ メンションポップアップ Tributeが期待した位置に表示されてくれない vue-mentionが表示されない vue-mentionの表示位置がおかしい 全角@で期待通りに動いてくれない 入力中の名前色付け textarea内の文字に色をつける方法がない スクロールバーの有無で色付け位置がズレる メンションポップアップ Tributeが期待した位置に表示されてくれない リリース当初よりTributeでメンションの動作を実装していました。通常の文字数や表示位置では問題なく動作していたのですが、画面全体のスクロール位置や入力文字数によって期待した位置に表示されない問題がありました。かなり試行錯誤をしたのですがうまくいかなかったため、vue-mentionに乗り換えることにしました。 vue-mentionが表示されない 乗り換えたところ、なぜか表示されませんでした。これがかなりハマりました。vue-mentionはv-tooltipに依存しているのですが、Bootstrapの.tooltipクラスとぶつかって、なんとopacity: 0;が採用されてしまっていました。 v-tooltip内の.tooltipに関しては、opacity: 1;が適用されるようにCSSを設定して回避しました。 [id^="popover_"].tooltip { opacity: 1; } vue-mentionの表示位置がおかしい いや違う、左側ではなく右側にでて欲しいのです。デモでは右に出ているのです。 細かい原因は調べませんでしたが、きっとまたBootstrapのCSSと競合しているのだろうと思いました。そこでまるっとv-tooltipのCSSが優先して適用されれば良いのではないかと考えました。 CSSは、対象を詳しく指定しているセレクタが優先されるので、idと合わせて指定することでオーバーライドしてやります([id^="popover_"].tooltip)。v-tooltipのデフォルトのCSSで上書きすることで、うまく解決しました。 [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 24px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, .1); } .popover-arrow { border-color: $color; } } &[aria-hidden='true'] { visibility: hidden; opacity: 0; transition: opacity .15s, visibility .15s; } &[aria-hidden='false'] { visibility: visible; opacity: 1; transition: opacity .15s; } } きました!(幅やカラー指定の調整は省略しています) 全角@で期待通りに動いてくれない 全角@でポップアップが出て、確定するとなんだか@がいっぱい出てきてしまいます。 ドキュメントに記載はないのですが、どうやら隠しオプションみたいなものがあって、omitKeyというのを見つけました。 これをセットすることで、起動キーを排除した上で、valueをセットできるようになりました。valueには@も含めることで、期待通り動作します。でも、まだもうひとつ@が残ってしまいます。 IMEの変換確定とメンションの決定が重なって、決定後に@が残ってしまっています。IME確定後のみ決定を発動させる必要がありそうです。 vue-mentionのコードをみてみるとonKeyDown()が担っているようです。この部分ですね。 if ((e.key === 'Enter' || e.key === 'Tab' || e.keyCode === 13 || e.keyCode === 9) && 非常にdirtyですがe.key === 'Enter' || e.key === 'Tab'を無効にするオーバーライドをすることで回避しました。しかし、keyCodeはdeprecatedなので、いずれ期待通り動かなくなるかもしれません。他サービスで全角@が気持ちよく動作しないものがあるのは、きっとこの辺りの事情があるのでしょう。英語圏の人たちには関係ないでしょうし・・。ところで、正しくはどう実装するべきなのだろう。 入力中の名前色付け textarea内の文字に色をつける方法がない さまざまなサービスで簡単そうに実現しているのですが、甘くみていました。textarea css font-colorなどとググってみても、どうやらtextarea内の特定の文字に色をつける方法はないようです。 そうするとcontenteditableが頭をよぎりますが、プレーンテキストとして取り扱うのはかなりしんどさがあります(以前とんでもなくハマったのですがここでは省略します)。 そこで、textareaに、入力中の文字とまったく同じ文字を表示するdivを重ねる方法を採用しました。 ピンクで表示したdiv内にtextareaで記述した内容をそのまま透明文字で表示して、必要に応じて<span>等でカラーをつける作戦です。仮実装してすぐ期待通りに動いたので、簡単そうに見えました。しかし・・・ スクロールバーの有無で色付け位置がズレる ああ・・なるほど。スクロールバーさんを忘れていました。サイズ変更などでも表示されますね。スクロールバー表示有無を検出してスクロールバー幅分をpaddingしてやる必要がありそうです。 const barWidth = this.$refs.comment.$el.getBoundingClientRect().width - this.$refs.comment.$el.clientWidth (実際はtextareaに適用されたボーダーなどのスタイルも加味して計算しています) うまくいきました。 完成形 <template> <div id="app"> <div class="p-3"> <div class="position-relative"> <div ref="textareaCover" class="textarea-cover" v-html="commentMention" ></div> <mentionable :keys="['@', '@']" :items="users" insert-space omit-key> <textarea ref="comment" class="form-control comment" rows="4" v-model="comment" @keyup="commentScroll" @scroll="commentScroll" /> <template #item="{ item }"> <div class="user"> <span class="font-weight-bold"> {{ item.value }} </span> <span class="ml-2"> {{ item.firstName }} </span> </div> </template> </mentionable> </div> </div> </div> </template> <script> import { Mentionable } from "vue-mention"; Mentionable.methods.onKeyDown = function (e) { if (this.key) { if (e.key === "ArrowDown" || e.keyCode === 40) { this.selectedIndex++; if (this.selectedIndex >= this.displayedItems.length) { this.selectedIndex = 0; } this.cancelEvent(e); } if (e.key === "ArrowUp" || e.keyCode === 38) { this.selectedIndex--; if (this.selectedIndex < 0) { this.selectedIndex = this.displayedItems.length - 1; } this.cancelEvent(e); } if ( (e.keyCode === 13 || e.keyCode === 9) && this.displayedItems.length > 0 ) { this.applyMention(this.selectedIndex); this.cancelEvent(e); } if (e.key === "Escape" || e.keyCode === 27) { this.closeMenu(); this.cancelEvent(e); } } }; export default { name: "App", components: { Mentionable, }, data() { return { comment: "", users: [ { value: "@akryum", firstName: "Guillaume", }, { value: "@posva", firstName: "Eduardo", }, { value: "@atinux", firstName: "Sébastien", }, ], }; }, computed: { commentMention() { if (typeof this.comment?.replaceAll !== "function") { return this.comment; } const replaced = this.comment ?.replaceAll("&", "&amp;") ?.replaceAll(">", "&gt;") ?.replaceAll("<", "&lt;") + "\n\n"; const search = new RegExp( this.users .slice() .sort((a, b) => b.value?.length - a.value?.length) .map((user) => user.value) .join("|"), "g" ); return replaced.replace(search, (match, offset) => { return `<span class="mention-str">${match}</span>`; }); }, }, methods: { mounted() { setTimeout(() => { this.resize(); window.addEventListener("resize", this.resize); this.$once("hook:beforeDestroy", () => { window.removeEventListener("resize", this.resize); }); }); }, resize() { const barWidth = this.$refs.comment.getBoundingClientRect().width - this.$refs.comment.clientWidth - 2; // border this.$refs.textareaCover.style.paddingRight = `calc(12px + ${barWidth}px)`; }, commentScroll() { this.$refs.textareaCover.scrollTop = this.$refs.comment.scrollTop; this.resize(); }, }, }; </script> [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 10px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, 0.1); } .popover-arrow { border-color: $color; } } &[aria-hidden="true"] { visibility: hidden; opacity: 0; transition: opacity 0.15s, visibility 0.15s; } &[aria-hidden="false"] { visibility: visible; opacity: 1; transition: opacity 0.15s; } } .mention-item { padding: 4px 10px; border-radius: 4px; } .mention-selected { background: #00ABE7; color: white; } .textarea-cover { z-index: 1; position: absolute; color: transparent; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: hidden; padding: 6px 12px; border: 1px solid transparent; } .comment { width: 100%; } .mention-str { color: #00ABE7; background-color: rgba(0, 171, 231, 0.05); } 実際のサービス上は血の滲むようなCSSの調整をがんばっております。もしかしたらまだバグがあるかもしれません。何かお気づきの点がありましたらお知らせいただけますと幸いでございます。 まとめ vue-mention + Bootstrap は css をオーバーライド(リセット)して使う 全角@問題はkeyCodeで解決する(ただしdepricatedなのが悩ましい) textareaに色付けできないので上に文字を重ねて表現する 簡単そうで実は難しい「普通の動き」の実装にとっても苦労してハマりました。これで気持ちよくメンションして受信トレイでサラサラと通知を管理できるようになりそうです。ますます便利になった「プロジェクト管理と情報共有のためのツール(ガントチャート無料)」Repsona、ぜひお試しください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pタグとwidthの相性の悪さの落とし穴にハマったので報告します

Pタグの横幅設定にwidthすれば余裕で横幅調整できると思っていたら落とし穴にハマったので ご報告します。 ↓普通のPタグで日本語文字と意図しない文字、そして数字を囲ってみました。 <p>おはようございます。こんにちは。こんばんは。おはようございます。こんにちは。こんばんは。おはようございます。こんにちは。こんばんは。おはようございます。こんにちは。こんばんは。</p> <p>hogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogohogo</p> <p>1234567891012345678910123456789101234567891012345678910123456789101234567891012345678910</p> p { width: 200px; border: 1px solid black; } 結果↓ 日本語文字は、widthにきっちり納まってくれてます。 意図しない文字と数字はwidthの横幅からはみ出てしまいました 次に、heightを設定したらどうなるか?検証してみます p { width: 200px; height: 250px; ※ border: 1px solid black; } きちんと縦幅、横幅は設定されています 検証ツールで確認しても同じです。 はみ出さない様に設定してあげるには p { width: 200px; height: 250px; border: 1px solid black; word-wrap: break-word; ※ } word-wrap: break-word;を設定したら無事『枠』に納まりました。 では、何故『意図しない文字と、数字』はwidhth設定の枠からはみ出てしまうのか。 色々調べてみたら、問題解決に参考になった記事がありましたのでリンクします。 文字がはみ出してしまう 参考: css – ブロックレベルから文字がはみ出してしまう 半角などの隙間がない英字(英単語の形ではないもの)だと、どこで折り返していいか分からず、そのまま伸びちゃってる現象です。  まとめ 半角で隙間のない英数字は、折り返し地点が不明になるので落とし穴にハマってしまう現象に陥ってしまう。 word-wrap: break-word; を設定してあげるか、隙間のある英数字とするか。 これで一応問題は解決しました。 pタグの文章がまだ決まっていない際「なんでも文章にしとおけば良い」とは限らずで 意外な部分にpタグとwidthの相性の悪さがありました。 ただ、問題は解決したので一安心 引き続き、codeを書き沢山エラーを出して解決力を向上させていきます。 今回は以上です ありがとうございます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

相席居酒屋風時計を作ってみた

はじめに 大阪を歩いていると、相席居酒屋の看板が目に入る時ありますよね。 お店によっては、現時点での男性客と女性客の人数が表示されてるのですが、 ほとんど女性客のほうが多いイメージがあって、これ実は時計なんじゃね...?って思ったんで 最近javascriptの勉強を始めだしたこともあり、勉強がてら作成してみました。 作成物 javascript やってることは、1秒毎に現在の時間、分を特定のIDを持つ場所に出力しているだけです。 const hoursClock = () => { const now = new Date(); const hours = now.getHours(); document.getElementById("hours").innerHTML = hours } const minutesClock = () => { const now = new Date(); const minutes = now.getMinutes(); document.getElementById("minutes").innerHTML = minutes } hoursClock(); minutesClock(); setInterval(hoursClock, 1000); setInterval(minutesClock, 1000); 終わりに すごく簡単なものではありますが、寝る前に思いついたものを1時間程度で形にできたので、楽しかったです。 更に勉強を重ねて、いつかは社会の役に立ちそうなものを作りたい。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【CSS】「CSSチョットデキル」に、CSSだけで虹色に光るアニメーションをつけてみた

概要 ネタです CSSのanimation, keyframesのみで文字を虹色に光らせる とりあえずコード See the Pen CSS チョットデキル by Shiba (@shiba_gsgs) on CodePen. 重要なのはanimationと@keyframesです。 animation animation: rainbow 3s infinite; 使う@keyframesの名前、アニメーション開始~終了までの秒数を指定しています。 infiniteをつけると無限にアニメーションを繰り返すようになります。 @keyframes @keyframes rainbow{ 0%{color:#ff0053;} 12%{color:#ff5353;} 24%{color:#ffcf53;} 36%{color:#e8ff53;} 48%{color:#53ff5d;} 60%{color:#53ffbc;} 72%{color:#5393ff;} 84%{color:#ca53ff;} 100%{color:#ff53bd;} } 開始時には#ff0053 、ほかの色を経由して終了時には #ff53bd になります。 参考記事 https://developer.mozilla.org/ja/docs/Web/CSS/animation https://developer.mozilla.org/ja/docs/Web/CSS/@keyframes https://qiita.com/7968/items/1d999354e00db53bcbd8
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む