20220117のHTMLに関する記事は3件です。

Twitterのシェアボタンを実装

目的 今回、以下の画像の下の方にあるTwitterシェアボタンの実装をどうやったかご紹介します。 同じように実装したいと思っている人のお役に立てれば嬉しいです。 Twitterのボタンをクリックすると別タブでTwitterのページが表示され以下の画像のように 文字やシェアしたいURLが表示されツイートできる状態になります。 このようにシェアしたいページをボタンを押せばシェアできるようになっています。 実装 結論から言うと以下のように実装しています 実装したいviewの中に 例>show.html.erb <%= link_to image_tag('twitterアイコン', class: :twitter_icon), "https://twitter.com/share?url=#{request.url} & text=#{@artist.name} のDIG ARTISTページ", data: { show_count: false }, title: 'Twitter', target: '_blank', rel: "noopener noreferrer" %> 簡単に言うと 画像にTwitterシェアのリンクを埋め込んでいます。 画像 まずボタンにしたい画像(ファイル名はなんでも大丈夫です)を app/assets/imagesに入れ <%= link_to image_tag('画像のファイル名') %> で画像を呼び出してます。 URL <%= "https://twitter.com/share?url=#{request.url}" %> でシェアしたいページを指定しています。 #{requests.url}で現在表示しているURLを入れていますが。 シェアしたいページが決まっているのであればそのページのURLを入れれば そのシェアしたいページのURLがシェア出来るようになります。 text textは入れても入れなくてもどちらでも大丈夫です。 URLの後に <%= "https://twitter.com/share?url=#{request.url} & text=入れたい文章” %> で好きな文章を入れる事ができます。 ハッシュタグ 今回の実装には入れていませんがハッシュタグも追加する事ができます。 URLの後に <%= "https://twitter.com/share?url=#{request.url} & hashtags=好きなハッシュタグ" %> 今回の実装に追加するとこんな感じです。 <%= link_to image_tag('twitterアイコン', class: :twitter_icon), "https://twitter.com/share?url=#{request.url} & text=#{@artist.name}のDIG ARTISTページ & hashtags=バンド", data: { show_count: false }, title: 'Twitter', target: '_blank', rel: "noopener noreferrer" %> 上の画像のようにハッシュタグが追加されました。 その他 target: '_blank' これを記述することで別タブで表示しています。 これを外すと同じタブで表示されます。 rel: "noopener noreferrer" こちらは外部サイトへのリンクのためセキュリティの観点から記述してます。 以下の記事参照 まとめ Twitterシェアボタンを実装したいviewに 〇〇.html.erb (テキストあり、ハッシュタグあり) <%= link_to image_tag('画像のファイル名', class: :クラス名), "https://twitter.com/share?url=#{request.url} & text=シェア時に入れたい文章 & hashtags=シェア時に入れたいハッシュタグ", data: { show_count: false }, title: 'Twitter', target: '_blank', rel: "noopener noreferrer" %> (テキストあり、ハッシュタグなし) <%= link_to image_tag('画像のファイル名', class: :クラス名), "https://twitter.com/share?url=#{request.url} & text=シェア時に入れたい文章", data: { show_count: false }, title: 'Twitter', target: '_blank', rel: "noopener noreferrer" %> (テキストなし、ハッシュタグなし) <%= link_to image_tag('画像のファイル名', class: :クラス名), "https://twitter.com/share?url=#{request.url} ", data: { show_count: false }, title: 'Twitter', target: '_blank', rel: "noopener noreferrer" %> 以上のようになります。 ぜひ試してみてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

canvasを使いお絵描きアプリを作る。(各機能ざっくり解説)

開発を進めていく上で意外とcanvasの知識が無かったので復習・備忘録も兼ねてcanvasの説明をしていきます。 コレちゃうねん何言ってんだこいつって点があれば教えてくださいまし。 目次 1.canvasとは 2.HTML側のコーディング 3.JS側のコーディング 4.各種ペンツール 5.プレビュー機能 6.クリア機能 7.ダウンロード機能 8.ソースコード全容 1. canvasとは いつもお世話になっているmozillaさんの引用となります。 HTML の 要素 と Canvas スクリプティング API や WebGL API を使用して、グラフィックやアニメーションを描画することができます。 簡単に言っちゃえばcanvasの中にアニメーションやグラフィックを描画することが可能です。 なのでマウスの動作を利用して署名動作を行う際に利用したり、WEBでお絵描きができます。 ※今回はスマホは非対応になっています。(対スマホコーデディングがめんどくさかったので) 完成したもの こちらになります。 グレーの背景にマウスドラッグするとペンの描画が始まって、絵やサインを書いてくれます。 下のPencil Color,Size,Opacityでペンの色、太さ、透過度を調整可能です。 また、ダウンロード、プレビュー、クリアボタンなどの各種機能を作ってみました。 これらの機能を順を追って解説していきます。 2. HTML側のコーディング HTML側のコーディングは至ってシンプルです。 今回はCSSのフレームワークにBuluma.cssを採用しています。 デザインがシンプルかつカッコよくてコーディングが短くて済むので、簡単なサンプルを作る際に重宝しています。 index.html <div class="columns"> <div class="column is-half"> <canvas id="canvasArea"></canvas> </div> <!-- 省略 --> </div> 本来canvasは高さ、幅を指定しないといけませんが今回はjs側に指定する挙動にしました。 共通クラスにする設計にした為です。 なので最小構成はこんな感じでイケます。 3. JS側のコーディング JS側は少し量が増えます。 といってもさほど大した量ではありません。 取得した要素に対してnullチェックを行っているのはカスタマイズ性を意識しています。 previewとかダウンロードいらないよ。ってのはあるかもしれないのでその為です。 コンストラクタ コンストラクタ部分はcanvasのid、幅、高さ、各種セレクターを指定します。 その上で初期化処理を実施します。 painter.js class Painter { constructor(selectorId, width, height, pencilSelector = { colorPencil: '#pencilColor', colorPalette: '.color-palette', pencilSize: '#pencilSize', pencilOpacity: '#pencilOpacity', clearButton: '#clearButton', downloadButton: '#downloadButton', previewButton: '#previewButton', previewArea: '#preview', }) { this.selectorId = selectorId; this.width = width; this.height = height; this.pencilSelector = pencilSelector this.x = null; this.y = null; this.init(); } /** * initialize function. */ init = () => { this.element = document.getElementById(this.selectorId); this.clearButton = document.querySelector(this.pencilSelector.clearButton); this.downloadButton = document.querySelector(this.pencilSelector.downloadButton); this.previewButton = document.querySelector(this.pencilSelector.previewButton); this.previewArea = document.querySelector(this.pencilSelector.previewArea); if (this.element == null) { throw Error('[painter.js] Selector is not found. Please specify the id.'); } if (this.element.tagName !== 'CANVAS') { throw this.error(`${this.selectorId} is not canvas`); } this.element.width = this.width; this.element.height = this.height; this.element.addEventListener('mousemove', this.onMouseMove); this.element.addEventListener('mousedown', this.onMouseDown); this.element.addEventListener('mouseout', this.drawFinish); this.element.addEventListener('mouseup', this.drawFinish); if (this.clearButton != null) { this.clearButton.addEventListener('click', this.clearCanvas); } if (this.downloadButton != null) { this.downloadButton.addEventListener('click', this.download); } if (this.previewButton != null) { this.previewButton.addEventListener('click', this.preview); } if (this.previewArea != null) { this.previewArea.src = './image/no-preview.jpg'; this.previewArea.width = this.width; this.previewArea.height = this.height; } this.context = this.element.getContext('2d'); this.setCanvasStyle(); // init pencil setting. this.penSize = 3; this.penColor = '#000000'; this.penOpacity = 1; this.initColorPencilElements(); } /** * set canvas style */ setCanvasStyle = () => { this.element.style.border = '1px solid #778899'; this.context.beginPath(); this.context.fillStyle = "#f5f5f5"; this.context.fillRect(0, 0, this.width, this.height); } // 省略 } const painter = new Painter('canvasArea', 564, 407); 解説:constractor 特にいう事がありません。 渡された引数を元に変数の初期化したり・・・などなど。 解説:init イベントハンドラの紐づけや引数のチェックを行います。 またhtmlのcanvas側に指定しなかった幅、高さの設定もこちらで実施します。 特記すべきところはこちらでしょうか。 painter.js this.element.addEventListener('mousemove', this.onMouseMove); this.element.addEventListener('mousedown', this.onMouseDown); this.element.addEventListener('mouseout', this.drawFinish); this.element.addEventListener('mouseup', this.drawFinish); ここでthis.element= canvasの要素に対してイベントハンドラを紐づけます。 スマホになるともう少しひと手間必要ですが、今回は省略します。。(めんどくさいのだ。。。) またこちらで呼び出されてるinitColorPencilElementsは後程解説します。 解説:setCanvasStyle canvasの初期設定をします。 またクリアボタン押下時にも呼び出される関数となります。 canvas要素のbeginPath()を呼び出してパスを開始します。 基本的にcanvasは任意の場所に点をおいて、繋げていく→線や図形や文字になる。といった概念です。 この辺りは説明がすんごくしんどいので公式を見てもらうと良いかもしれません。 その為こちらでは説明は割愛しますが、描画を開始する際に呼び出される関数といった形で覚えてください。 (分かりやすい説明があればコメントお待ちしています。) 描画処理 init()で設定されたイベントハンドラの中で動く関数の説明になります。 ここからが本題となります。 painter.js /** * Calculate the coordinates from the event. * @param {*} event */ calcCoordinate = (event) => { const rect = event.target.getBoundingClientRect(); const x = ~~(event.clientX - rect.left); const y = ~~(event.clientY - rect.top); return {x, y}; } /** * mouse down event * @param {*} event */ onMouseDown = (event) => { if (event.button !== 0) { return; } const coordinate = this.calcCoordinate(event); this.draw(coordinate); } /** * mouse move event * @param {*} event */ onMouseMove = (event) => { if (event.buttons !== 1) { return; } const coordinate = this.calcCoordinate(event); this.draw(coordinate); } /** * End of drawing process. */ drawFinish = () => { this.x = null; this.y = null; } /** * drawing process * @param {*} coordinate */ draw = (coordinate = {x: 0, y: 0}) => { const {x: toX, y: toY} = coordinate; this.context.beginPath(); this.context.globalAlpha = this.penOpacity; const fromX = this.x || toX; const fromY = this.y || toY; this.context.moveTo(fromX, fromY); this.context.lineTo(toX, toY); this.context.lineCap = 'round'; this.context.lineWidth = this.penSize; this.context.strokeStyle = this.penColor; this.context.stroke(); this.x = toX; this.y = toY; } 解説:onMouseDown マウス押下時に発火されるイベントです。 マウスの座標を計算し、描画処理を行う関数です。 見ての通り処理の詳細はcalcCoordinate()とdraw()に集約されています。 解説:onMouseMove mouseDownとほぼ同じです。 ですがmouseDown時はevent引数の状態が違うので差別化する必要があります。 こちらも処理の詳細はcalcCoordinate()とdraw()に集約されています。 解説:calcCoordinate イベント引数からマウスの座標を計算します。 単純にclientX, clientYだけの計算だとページがスクロールや拡大・縮小した際に座標の位置が狂います。 それを防ぐためgetBoundingClientRectにて要素に対するウィンドウ座標を出力してあげて、計算してあげる必要があります。 これによってスクロールした際に点が明後日の方向にいかないように制御することができます。 最終的にチルダ2つで小数点を省き絶対値のみの値にしてx,y座標を返します。(そこまで細かい描画はしない為) 解説:draw 引数はcalcCoordinateの戻りです。 先程軽く触れましたが描画にはパスの繋ぎが必要になりますので、fromX,toX, fromY, toYの4つの変数が重要になります。 painter.js // 座標の変数を展開して const {x: toX, y: toY} = coordinate; // ここで開始の座標を指定する。this.x, this.yがnullならばto === fromになるので値を代入 const fromX = this.x || toX; const fromY = this.y || toY; // 座標の開始位置まで移動 this.context.moveTo(fromX, fromY); // 座標の終了位置までパスをつなぐ this.context.lineTo(toX, toY); 上記コードでパスを繋ぎました。 実際の色を塗る処理はこちらになります。 painter.js this.context.lineCap = 'round'; // 丸形のペン(今回は固定にしました。) this.context.lineWidth = this.penSize; // ペンサイズを指定 this.context.strokeStyle = this.penColor; // ペンの色を指定 this.context.stroke(); // 描画する つたない説明になってしまいましたが、こんな感じです。 4. 各種ペンツール 無駄に拘ってしまったペンツールのご紹介。 init()で省略したペンのスタイルについてです。 painter.js /** * Initialize colored pencils */ initColorPencilElements = () => { const { colorPencil: color, colorPalette: palette, pencilSize: size, pencilOpacity: opacity } = this.pencilSelector; const colorPencil = document.querySelector(color); const colorPalette = document.querySelector(palette); const pencilSize = document.querySelector(size); const pencilOpacity = document.querySelector(opacity); if (colorPencil != null) { colorPencil.value = this.penColor; colorPencil.addEventListener('click', (ev) => { ev.target.type = 'color' }); colorPencil.addEventListener('blur', (ev) => { ev.target.type = 'text'; if (colorPalette != null) { colorPalette.style.backgroundColor = ev.target.value; } }); colorPencil.addEventListener('change', (ev) => { this.penColor = ev.target.value; }); } if (colorPalette != null) { colorPalette.style.backgroundColor = this.penColor; } if (pencilSize != null) { pencilSize.value = this.penSize; pencilSize.addEventListener('change', (ev) => { this.penSize = ev.target.value; }); } if (pencilOpacity != null) { pencilOpacity.value = this.penOpacity; pencilOpacity.addEventListener('change', ev => { this.penOpacity = ev.target.value; }); } } 解説:initColorPencilElements ペンのスタイルを定義します。それだけならまだいいのですが、、、 標準のinput type="color"が死ぬほどダサくてカスタマイズしました。 基本的にブラウザ標準のがダサすぎるんですよね・・・。 inputに色がビーって引っ張る辺りが相当ダサくて・・。 なのでこんな感じでクリックしたらcolorに変化して、blurしたらtextにするようにしました。 そしてchangeイベントで横のBOXの色が変化するようにしてみました。 これらの設定は一旦クラス変数に保持してあげて終わり。draw()時にその設定を利用するようにしています。 5. プレビュー機能 画像を一旦プレビューしたい時ってありますよね。 隣にあるから需要が少ないと思いますが、Google Lens等の画像検索したい時に便利です。 簡単ですね。imageタグの属性を変えてあげるだけです。 index.html <div class="column is-half preview-area"> <img id="preview" /> </div> painter.js /** * show preview */ preview = () => { this.previewArea.src = this.element.toDataURL(); // canvasの要素をdataURLに変換 // 高さ・幅調整 this.previewArea.width = this.width; this.previewArea.height = this.height; } 6. クリア機能 本来なら要素をクリアするだけで良いのですが、今回はcanvasなのでそうはいきません。 clearRect()で開始位置のx,y座標は0を指定し、終了位置は要素の幅、高さを指定しましょう。 そしてthis.setCanvasStyle();を再度呼び出してあげれば元の状態に戻ります。 painter.js /** * clear canvas */ clearCanvas = () => { this.context.clearRect(0, 0, this.element.width, this.element.height); this.setCanvasStyle(); } 7. ダウンロード機能 描画した画像をダウンロードします。 blob形式に変えて動的URLを生成してクリック動作を発火させます。 クライアント側ダウンロードと同じ挙動ですね。 painter.js /** * download image. */ download = () => { this.element.toBlob((blob) => { const url = URL.createObjectURL(blob); const aTag = document.createElement('a'); document.body.appendChild(aTag); aTag.download = 'drawImage.png'; aTag.href = url; aTag.click(); aTag.remove(); URL.revokeObjectURL(url); }); } 8. ソースコード全容 こちらが該当のソースになります。 https://github.com/kinachan/canvasSample 公開するときに色々と無駄なコードが残っていて焦りました。 もっと発信が出来るように頑張ろうとおもいました。  余談 今回はVanilla縛りだったので仕方ないのですがTypescriptで書いた方がもっときれいに書けるのに・・と思いました。 やっぱりTypescriptはつよい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザだけで(npm を使わないで)VRMを表示する+簡単な操作

 ブラウザで VRM を表示するためのライブラリとして、pixivが three-vrm を公開してくれています。公式のビルド手順は html を直書きするものと、npm を使うものがあります。npm を使う方については three.js で VRM を表示する で詳しく解説してくださっていますので、本記事では html を直書きする方法を解説したいと思います。html や JavaScript 何もわからんマン(私もそれに近いですが)でもなんとかなるように書くつもりです。 完成イメージ 完成ツリー  フォルダ直下に以下のファイルが並ぶことになります。サブフォルダとかはないのでわかりやすいです。というか、フォルダとして完結してなくても、index.htmlと同じ階層にそれ以外の4つのファイルがあれば大丈夫です。 AliciaSolid.vrm GLTFLoader.js index.html three.js three-vrm.js 素材あつめ  以下の4つの素材が必要です。 AliciaSolid.vrm  ニコニ立体からDLしてください。もちろん、好みの VRM が手元にある人はそれに差し替えてもいいです。 three.js  https://github.com/mrdoob/three.js/blob/master/build/three.js をDLしてください。 GLTFLoader.js  https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/GLTFLoader.js をDLしてください。 three-vrm.js  three-vrm.js のビルドデータ からDLしてください。注意点として、「名前をつけて保存」だと js ファイルなのに html のような形で保存されてしまい、上手くいきません。下の「View Raw」からコピペするのが確実だと思います。  エディタ上で開ける人は、エラーがないかを確認するのが確実です。 HTML作成  次にindex.htmlというファイルを作成します。公式ガイドによれば以下の文をコピペします。 <script src="three.js"></script> <script src="GLTFLoader.js"></script> <script src="three-vrm.js"></script> <script> const scene = new THREE.Scene(); const loader = new THREE.GLTFLoader(); loader.load( // URL of the VRM you want to load '/models/three-vrm-girl.vrm', // called when the resource is loaded ( gltf ) => { // generate a VRM instance from gltf THREE.VRM.from( gltf ).then( ( vrm ) => { // add the loaded vrm to the scene scene.add( vrm.scene ); // deal with vrm features console.log( vrm ); } ); }, // called while loading is progressing ( progress ) => console.log( 'Loading model...', 100.0 * ( progress.loaded / progress.total ), '%' ), // called when loading has errors ( error ) => console.error( error ) ); </script>  これは JavaScript の部分だけなので、他に head とか body とかを追記する必要があります。  また、実はこれだけではキャンバスに描画するには不十分です。上のコードはデータを読み込んでいるだけで、どのカメラからどこを写すか、みたいな情報はないのです。そのため、Three.js の追加命令として、 カメラ ライティング レンダラー アニメーション  といった処理を加える必要があります。  結論的には以下のようになります。 <!DOCTYPE html> <html> <head> </head> <body> <script src="three.js"></script> <script src="GLTFLoader.js"></script> <script src="three-vrm.js"></script> <script> // シーンの準備 const scene = new THREE.Scene() // カメラの準備 const camera = new THREE.PerspectiveCamera(45, 960 / 540, 0.1, 1000) camera.position.set(0, 1.4, -1.0) camera.rotation.set(0, Math.PI, 0) // レンダラーの準備 const renderer = new THREE.WebGLRenderer() renderer.setSize(960, 540) document.body.appendChild(renderer.domElement) // ライトの準備 const directionalLight = new THREE.DirectionalLight('#ffffff', 1) directionalLight.position.set(1, 1, 1) scene.add(directionalLight) const loader = new THREE.GLTFLoader(); loader.load( // URL of the VRM you want to load 'AliciaSolid.vrm', // called when the resource is loaded (gltf) => { // generate a VRM instance from gltf THREE.VRM.from(gltf).then((vrm) => { // add the loaded vrm to the scene scene.add(vrm.scene); // deal with vrm features console.log(vrm); }); }, // called while loading is progressing (progress) => console.log('Loading model...', 100.0 * (progress.loaded / progress.total), '%'), // called when loading has errors (error) => console.error(error) ) // アニメーションループの開始 function tick() { requestAnimationFrame(tick) renderer.render(scene, camera) } tick() </script> </body> </html>  コードについては three.js超入門 第1回 レンダリングまでの流れ について大元をお借りしました。  この時点で、index.htmlを開くと上手くいけば以下のように表示されているはずです。  なお、自分で用意した VRM を表示させたい方は、AliciaSolid.vrmの部分を対応する名前に変更してください。 上手くいかない場合 キャンバス(黒枠)が表示されない  上に書いた手順を間違えている可能性があります。 キャンバス(黒枠)はあるが人間が表示されない  実は、上の手順を忠実になぞった人のほとんどがこちらになっていると思われます。というのも、ブラウザによっては、ローカルファイルでは外部ファイル(つまりAliciaSolid.vrm)を読み込めないのです。ちなみに大体のブラウザはそうです。セキュリティ上の対策らしいです。  これを回避するにはローカルサーバーを立てる、という儀式を経る必要があります。色々手段はありますが、一番簡単だと思われる、VSCode を用いた手段を解説します。  VSCode をDLして、  LiveServer という拡張機能をインストールして、  index.htmlを開いた状態で「Go Live」ボタンを押します。  そうすると、ローカルサーバーが立ち上がります。ブラウザのURLがfile:///C:/Users/xxx/xxx/index.htmlではなくhttp://127.0.0.1:5500/index.htmlになっていれば成功です。 表情やポーズを変化させる  無事読み込めましたが、無表情なTポーズなので変化を加えたいです。 表情を変える  以下のコードを加えます。 vrm.blendShapeProxy.setValue(THREE.VRMSchema.BlendShapePresetName.Joy, 1.0) vrm.blendShapeProxy.update() ポーズを変える  こちら少し複雑になります。各ボーンを取得して、回転させるという手順になります。複数行になるので、以下のような別関数を用意します。 const normalPose = (vrm) => { const leftUpperArm = vrm.humanoid.getBoneNode(THREE.VRMSchema.HumanoidBoneName.LeftUpperArm) leftUpperArm.rotateZ(1.2) const rightUpperArm = vrm.humanoid.getBoneNode(THREE.VRMSchema.HumanoidBoneName.RightUpperArm) rightUpperArm.rotateZ(-1.2) }  これを VRM をロードした時に作用させます。 normalPose(vrm); 結果  余裕がある人は、ポーズや表情を色々変えて遊んでみましょう。表情については https://pixiv.github.io/three-vrm/docs/enums/vrmschema.blendshapepresetname.html に、ボーンの名前については https://pixiv.github.io/three-vrm/docs/enums/vrmschema.humanoidbonename.html に詳しく載っています。 コード全体  index.htmlの最終的なコードは以下のようになります。 <!DOCTYPE html> <html> <head> </head> <body> <script src="three.js"></script> <script src="GLTFLoader.js"></script> <script src="three-vrm.js"></script> <script> // シーンの準備 const scene = new THREE.Scene() // カメラの準備 const camera = new THREE.PerspectiveCamera(45, 960 / 540, 0.1, 1000) camera.position.set(0, 1.4, -1.0) camera.rotation.set(0, Math.PI, 0) // レンダラーの準備 const renderer = new THREE.WebGLRenderer() renderer.setSize(960, 540) document.body.appendChild(renderer.domElement) // ライトの準備 const directionalLight = new THREE.DirectionalLight('#ffffff', 1) directionalLight.position.set(1, 1, 1) scene.add(directionalLight) const loader = new THREE.GLTFLoader(); loader.load( // URL of the VRM you want to load 'AliciaSolid.vrm', // called when the resource is loaded (gltf) => { // generate a VRM instance from gltf THREE.VRM.from(gltf).then((vrm) => { // add the loaded vrm to the scene scene.add(vrm.scene); // deal with vrm features console.log(vrm); // 筆者追記、両手を下ろす normalPose(vrm); // 筆者追記、笑顔に vrm.blendShapeProxy.setValue(THREE.VRMSchema.BlendShapePresetName.Joy, 1.0) vrm.blendShapeProxy.update() }); }, // called while loading is progressing (progress) => console.log('Loading model...', 100.0 * (progress.loaded / progress.total), '%'), // called when loading has errors (error) => console.error(error) ) const normalPose = (vrm) => { const leftUpperArm = vrm.humanoid.getBoneNode(THREE.VRMSchema.HumanoidBoneName.LeftUpperArm) leftUpperArm.rotateZ(1.2) const rightUpperArm = vrm.humanoid.getBoneNode(THREE.VRMSchema.HumanoidBoneName.RightUpperArm) rightUpperArm.rotateZ(-1.2) } // アニメーションループの開始 function tick() { requestAnimationFrame(tick) renderer.render(scene, camera) } tick() </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む