20200916のJavaScriptに関する記事は22件です。

addEventListenerの第2引数によってthisは変わる

TypeScript(またはJavaScript)で EventTarget.addEventListener() を使用する場合、第2引数の指定方法によって実行時の this の中身が変わります。
今更ながら地味にはまったため、自分用にメモしておきます。

確認

確認用にコードを作成しました。
https://playcode.io/671961

EventTarget.addEventListener() 第2引数の指定方法によって、関数show 実行時の this は以下のようになります。

script.ts
class ThisTest {
  constructor() {
    this.bindEvent();
  }

  // この関数を呼び出して、thisの中身を判定する。
  show(e: Event) {
    console.log(this.constructor.name);
  }

  bindEvent() {
    const t1 = document.querySelector('#test1');
    t1.addEventListener('click', this.show);
    // => 'HTMLButtonElement'

    const t2 = document.querySelector('#test2');
    t2.addEventListener('click', this.show.bind(this));
    // => 'ThisTest'

    const t3 = document.querySelector('#test3');
    t3.addEventListener('click', (e: Event) => this.show(e));
    // => 'ThisTest'

    const t4 = document.querySelector('#test4');
    t4.addEventListener('click', function (e: Event) {
      console.log(this.constructor.name);
      // => 'HTMLButtonElement'

      this.show(e);
      // => 'error: Uncaught TypeError: this.show is not a function'
      // this.show(e)の `this` がボタン要素を示すため、エラーが発生する。
    });
  }
}

new ThisTest();
index.pug
button#test1 テスト1
button#test2 テスト2
button#test3 テスト3
button#test4 テスト4

所感

EventTarget.addEventListener() に限った話ではないですが、はまらないように注意したいですね。

参考文献

JavaScript で陥りやすい失敗例を振り返る
EventTarget.addEventListener()

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue/Nuxtを使っている開発チームへ参画するまでの学習ロードマップ

はじめに

今回はVue.js/Nuxt.jsを用いて開発しているプロジェクトへ参画するまでに、どういったことを学習すれば良いかについて話していこうと思います。
私自身、8月からVue.js/Nuxt.jsを用いて開発しているチームへ配属されフロントエンドの開発をさせていただいておりますので、実際にどういった勉強をしてきたのかを書いていきます。

また、私の場合はVue/Nuxtチームに参画することになると概ね決まったのが参画の1ヶ月前でしたので、約1ヶ月間の学習となります。

さて、何からはじめよう?

最初どの教材で勉強するかについては、私的には特にこだわりはなかったので、周りで良い評判を聞いていた下記のUdemyの講座を購入して学習しました。

超Vue.js 2 完全パック - もう他の教材は買わなくてOK! (Vue Router, Vuex含む)

上記でVue.jsについてざーっと勉強した後は、下記のUdemy講座でNuxt.jsについても軽く勉強しました。

Nuxt JS入門決定版!Vue.jsのフレームワークNuxt JSの基本からFirebaseと連携したSPAの開発まで

※ 一応知らない方のために言っておきますと、Udemyは月に2,3回程セールをやっている期間がありますので、購入する際はその期間に買いましょう。めちゃめちゃ安くなります。

実際の現場ではどういう手法で開発しているかを知ろう

実際に参画することになるチームの方と連絡をとることができないのであれば仕方ありませんが、連絡可能であるならば事前にどういった勉強をすれば良いか聞いてみると良いでしょう。
そこで色々とリサーチすることができたら、あとはそのことについてひたすら勉強していきましょう。
(上記のUdemy講座などで学習する前に先に聞いても、順番はどちらでも構いません。)

私のチームの場合は、Atomic Designをベースに開発していて、UIフレームワークにはVuetifyを使っていたり、細かいところで言うとstoreへのアクセスはヘルパー関数を使っているということを聞いたので、その部分について学習していきました。
また、「Vue/Nuxtを使って2画面くらいの何かしらのツールを作ってみてください」と課題までくださったので、簡単なTodoアプリの作成も行いました。
そして作ったTodoアプリをレビューしていただいてアドバイスをもらい、修正などを行うことで、より理解を深めることができました。
(流石にここまでやってくださる会社さんはあまりないかも知れませんが。。)

そうして勉強しているうちにチームに参画されました。

さいごに

結局のところ、チームに参画してスムーズに開発を始めていくには、そのチームの方に聞いて教えていただいた内容を学習することだと思います。
私はこれで、Vue/Nuxtを全く知らないところからかなりスムーズに開発に混ざることができたかなと思います。

以上で短いですが、ご参考になれば幸いです。
最後までお読みくださりありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sapper × microCMS × Netlifyで始めるJAMstackブログ

やること

Gatsby.js, Nuxt.jsでJAMstackなサイトを作ったことはあったけど、最近使い初めてるSapperでは作ったことなかったので、実際に作ってみることにした。
Headless CMSはContentful, NetlifyCMSなど有名なものがありますが、今回は日本製のmicroCMSを使うことにしました。日本製とのことだけあって、管理画面が日本語ですごく見易くていい感じです。
デプロイ先は安定のNetlifyです。

そもそもJAMstackって?

JavaScript, API, Markupで構成されたアーキテクチャ

microCMSで新規プロジェクトを作成

今回はSapperからmicroCMSで作成したサービスを表示するのがメインなので、省略します。サービスについての詳しい作成は公式のmicroCMSのはじめ方をご覧ください。

APIの設定についてですが、今回は最低限の設定にしております。
API型:リスト型
スキーマ情報は以下の内容になります。
スキーマ情報

サービスを作成したら、Sapper側に表示するためのコンテンツを新規作成してみましょう!内容はわかりやすいもので大丈夫です。

自分はこんな感じで作成しました。
post

SapperでmicroCMSのコンテンツを取得する

microCMS側の準備はできたので、次にSapperの準備をしていきます。
まずは新規作成と、環境変数を取り扱うのでdotenvもインストールしていきます。

npx degit "sveltejs/sapper-template#rollup" sapper-jam
cd sapper-jam
npm install
npm run dev & open http://localhost:3000
npm install dotenv

そして環境変数用の設定をrollup.config.jsに記載します。

...
import dotenv from 'dotenv';

dotenv.config();

const api_key = process.env.API_KEY;

export default {
    client: {
        input: config.client.input(),
        output: config.client.output(),
        plugins: [
            replace({
                'process.browser': true,
                'process.env.NODE_ENV': JSON.stringify(mode),
                 'process.env.API_KEY': JSON.stringify(api_key) # ここを追加してあげてください。

microCMSのAPIリファレンスにあるX-API-KEYを.envファイルに設定します。

API_KEY=XXXXXXXXXXXX

Sapperのテンプレートにはsrc/routes/blog/にブログ用のテンプレートが存在します。
今回はこちらを使って、microCMSのコンテンツを表示していきます。
まずは一覧表示の部分を変えていきましょう。
src/routes/blog/index.svelteを以下のように編集してください。

<script context="module">
    export function preload({ params, query }) {
    return this.fetch(`https://your-service-name.microcms.io/api/v1/your-endpoint`, { headers: { 'X-API-KEY': process.env.API_KEY  } }).then(r => r.json()).then(posts => {
            return { posts };
        });
  }


</script>

<script>
    export let posts;
</script> 

...

<svelte:head>
    <title>Blog</title>
</svelte:head>

<h1>Recent posts</h1>

<ul>
  {#each posts.contents as post}
    <li>{post.title}</li>
    <a rel='prefetch' href='blog/{post.id}'>リンク</a>
    {/each}
</ul>

※APIのURL部分は自分のものを使うようにお願いします。

APIの取得結果をposts入れて、eachで一覧表示しています。これはもともとのテンプレートのものを差し替えてるだけなので、あまり変わりません。

個別詳細ページ用のslug部分はidがuniqueで都合がよかったのでそのままidを設定しています。

これで画面側を確認するとこんな感じで一覧が表示されてるかと思います。

一覧

最後に個別詳細ページの設定を行います。
src/routes/blog/[slug].svelteを以下のように編集します。

<script context="module">
    export async function preload({ params, query }) {
    const res = await this.fetch(`https://your-service-name.microcms.io/api/v1/your-endpoint/${params.slug}`, { headers: { 'X-API-KEY': process.env.API_KEY } });
        const data = await res.json();

        if (res.status === 200) {
            return { post: data };
        } else {
            this.error(res.status, data.message);
        }
    }
</script>

<script>
    export let post;
</script>

...

<svelte:head>
    <title>{post.title}</title>
</svelte:head>

<h1>{post.title}</h1>

<div class='content'>
    {@html post.body}
</div>

idを渡している部分は、個別詳細ページではparams.slugとして受け取ってるみたいです。
その他はとくに変わったところはありません。
画面の方を確認してみましょう。

個別詳細ページ

表示されましたね!

Netlifyにデプロイ

それではNetlifyにデプロイします。
今回のプロジェクトをGitHubに上げてる状態でNetlifyの管理画面からNew site from Gitを押して今回作成した、ブログプロジェクトを選択します。

ビルドコマンドと公開ディレクトリは以下の内容で設定してデプロイ。

Build command: npm run export
Publish directory: sapper/export

※サイトの設定からAPI_KEYの環境変数を設定することもお忘れずに

この時点でサイトは表示されているかと思いますが、このままだとmicroCMSで新規にコンテンツ作成しても反映されないので、microCMSとNetlifyをWebhookします。

Netlifyの作成したサイトからSettings→Build & deploy→Build HooksでURLを生成。
そのURLをコピーして、microCMSのAPI設定→WebhookにURLを追加すれば、新規にコンテンツを追加してもNetlifyで自動ビルドするようになります。

実際に新規追加してデプロイ一覧にmicroCMSのものがあれば成功です。

microCMS

おわりに

こんな感じで簡単にSapperからmicroCMSのデータを表示、Netlifyへのデプロイをすることができました。
Sapper自体もStaticGenにのっている他のStatic Generatorよりも比較的に使いやすいと思うので、是非試してみてください。

参考資料

microCMSのはじめ方
JamStack.org

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webアプリパターンの歴史 - SST、AJAX、CSR、SSR、SSG、そしてISR

これはりあクト! TypeScriptで始めるつらくないReact開発 第3版Ⅰ. 言語・環境編 読書会のLT用資料として作成しました。

Webアプリの動作するパターンをまとめました。歴史を振り返ることで JAMStack といったモダンなスタックがどういった点で有用なのか理解していきましょう。

発表者:@kimizuy
日々スプラのXPをどう上げるかに頭を悩ませている。


SST (1/3)

概要

  • Server Side Templating の略
  • 正確にはサーバサイドレンダリングだが、当時はこの手法しかなかったのでこの呼び方ではなかった
  • 主役は Ruby などのサーバーサイド、HTML や JavaScript は脇役
  • サーバーのレスポンスを受けると画面がクリアされ新しいページがレンダリングされる
  • 内容の更新にはリロードなどが必要

SST (2/3)

gen1st.png

出典:渋川よしき『Real World HTTP ミニ版』図5-6

サーバサイドでデータをテンプレートに流し込んでHTMLを生成し、それをブラウザに返す


SST (3/3)


AJAX (1/2)

概要

  • Asynchronous JavaScript + XML の略
  • IE 製の XMLHttpRequest API が発端
  • 次いで Server-sent Event、WebSocket、fetch といった通信APIが規格化される
  • フロントエンドの仕事が増大して、JQuery で DOM を操作したり、
  • 状態管理のために Backbone.js などのクライアントサイドMVCフレームワークが導入されたりする
  • XML ってついてるけど JSON が一般的なデータフォーマット

AJAX (2/2)

gen2nd.png

出典:渋川よしき『Real World HTTP ミニ版』図5-7

GET や POST メソッドを利用してウェブ API からデータを受け取り、HTML を動的に更新する


CSR (1/3)

概要

  • Client Side Rendering の略
  • SPA (Single Page Application) の始まり
  • React、Vue.js、Angular といったライブラリ・フレームワークが開発される
  • AJAX 環境よりも充実したライブラリ・エコシステム
  • コンポーネント指向
    • 単一責任の原則:「ひとつのコンポーネントは理想的にはひとつのことだけをするべきだ」
    • ロジックを分離してカプセル化し保守性を向上
    • コードの再利用性を高める

参考:React の流儀


CSR (2/3)

gen3rd.png

出典:渋川よしき『Real World HTTP ミニ版』図5-7

  • すべてのロジック、データフェッチ、テンプレーティングやルーティングがクライアントで行われる
  • 最初にほぼ空の HTML を読みこみ、JavaScript から DOM を生成・操作する
  • 仮想 DOM 機能(React、Vue)で差分だけを高速で更新が可能
  • History API というブラウザ API をラップした Router ライブラリでページ遷移を擬似的に表現でき UX が向上

CSR (3/3)

デメリット

  • アプリが大きくなると JavaScript ソースコードも肥大化し、クライアントで JavaScript を読み込むため クライアントの処理負荷や通信量の増大
  • ほぼ白紙の HTML と JavaScript を受け取ってからページを生成するため、すべてのコンテンツが表示されるまでの待機時間(TTI)が長い
  • 上記を考慮した遅延ロード処理が必要
  • SEOに不向き

SSR (1/3)

  • Server Side Rendering の略
  • Next.js や Nuxt.js といったフレームワークがある
  • 今言われている SSR は厳密には CSR と SSR の組み合わせ

SSR (2/3)

gen35th.png

出典:渋川よしき『Real World HTTP ミニ版』図5-8

  • サーバー側で JavaScript を実行するため、ユーザー側の処理能力に依存しない
  • サーバーがリクエストを受けた時点で初期データをもとにHTMLを生成し、それをブラウザに返す
  • 上記以降は CSR で DOM を更新する
  • 生成済みの HTML を受け取るため高速なローディングが可能
  • SEO に対応

SSR (3/3)

デメリット

  • リクエストを受けてから HTML を生成し、送信するので初期表示が遅くなる
  • 実装が複雑になりがち(だがフレームワークで回避可能)

サーバーレンダリングを「正しく」行うためには、コンポーネントのキャッシュ、メモリ消費の管理、メモ化の適用、その他多くの懸念事項に対するソリューションの検索や構築が必要です
Rendering on the Web


SSG (1/3)

概要

  • Static Site Generation の略
  • Next.js、Gatsby.js、Hugo、Jekyll といったフレームワークがある
  • データベースやサーバサイドを使わずにフロントエンドのみで完結できる
  • JAMStack で利用される

SSG (2/3)

ssg-host-flow.png

出典:https://www.netlify.com/blog/2020/04/14/what-is-a-static-site-generator-and-3-ways-to-find-the-best-one/

  • ビルド時にあらかじめ静的なファイルを生成する
  • また各 URL に対応する個別の HTML ファイルを前もって作成する
  • ホスティングサービス (Netlify や Vercel) で生成済みのファイルをホスティングするだけなので運用コストもかかりにくい
  • 初期表示時、クライアントは完成済みの HTML を受け取るだけのためパフォーマンスが高い
  • Next.js や Gatsby.js は SSG に加えて SSR や CSR を組み合わせて構築可能
  • SEO にも対応

SSG (3/3)

デメリット

静的レンダリングの欠点のひとつは、すべての有効なURLに対し、個々にHTMLファイルを生成しなければならないことです。 これらのURLが事前に、また多くの固有ページを持つサイトであるか予測できない場合、これは難題であり実行不可能になることもあります
Rendering on the Web


そして ISR へ (1/4)

  • Incremental Static Regeneration の略
  • 日本語で直訳すると「増分静的再生成」
  • ビルド後に静的生成されたページを追加したり、更新したりできる
  • Next.js 9.5 より正式な機能としてリリースされた

そして ISR へ (2/4)

whatisisr.png

https://arunoda.me/blog/what-is-nextjs-issg

  • getStaticProps() のなかに外部データをフェッチするための処理を書くと、ビルドサーバー(Node.js環境)で実行され、そのデータをもとに静的ページが生成される

  • getStaticPaths()にページIDを渡すことで個別のページを事前生成


そして ISR へ (3/4)

ページ追加と更新

無数にあるページを事前に生成しておくと、ビルドが非常に遅くなる
結局、ページの更新に CSR や SSR が必要
→ Incremental Static Regeneration で解決!

  • リクエスト時に初めてページを生成する
  • 次のアクセス以降は、生成したページを返す
  • 影響を受けるページのみ更新する機能
  • revalidate に秒数を指定することで更新頻度を変更可能
// pages/products/[id].js

export async function getStaticProps({ params }) {
  return {
    props: {
      product: await getProductFromDatabase(params.id)
    },
    unstable_revalidate: 60
  }
}

そして ISR へ (4/4)

デモ

  • 無数にあるツイートのうち一つのツイートを静的なページとして生成するデモ

static-tweet

  • GitHub のリアクションを逐次更新して、静的ページに反映するデモ

reactions-demo


参考にした文献やサイト

『Real World HTTP ミニ版』
https://www.oreilly.co.jp/books/9784873118789/
Real World HTTP 第2版も!)

React の流儀
https://ja.reactjs.org/docs/thinking-in-react.html

Rendering on the Web - Web上のレンダリング
https://developers.google.com/web/updates/2019/02/rendering-on-the-web?hl=ja#csr

AJAX
https://developer.mozilla.org/ja/docs/Web/Guide/AJAX

Next.js: Server-side Rendering vs. Static Generation
https://vercel.com/blog/nextjs-server-side-rendering-vs-static-generation


ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSのグラフライブラリを今選ぶならHighchartsで決まり(2015年2月版)

(元記事を更新したのでこちらに退避)
(更新した2020年9月版はこちら
.
.
.

更新記事を書きました。(2017年1月)
http://grgrjnjn.blogspot.jp/2017/01/jshighchartsgoogle-charts.html

.
.
.

グラフライブラリは多種多様に存在する。google trendで、highcharts、jqplot、amcharts、chart.jsなど、今選択するなら何がよいのか調べてみた。

結論

Highchartsを採用。商用利用は有償。

理由

圧倒的に使われている(っぽい)から。Googleトレンドでの他のライブラリとの比較。

Highcharts公式サイト

http://www.highcharts.com/

参考になるサイト

商用は有償

Highcharts - Single Website License : 13,501円
Highcharts Single Developer License : 59,070円
http://www.altech-ads.com/Others/Highcharts.htm?gclid=CLOoiPif1MMCFRUGvAodPAEA0g

デモ

http://www.highcharts.com/demo

Highcharts_demos.png

調査メモ

highcharts

  • 動作にはjQueryが必要(v3.0.5からはjQueryなしで利用可能)
  • ベクターデータでグラフ描画できる。
  • とにかく綺麗。
  • オプションしだいで細部までいじれるので好き。
  • Gemもあるのでとにかく便利です。lazy_high_charts このGemを使う際はController上に直接書くとControllerをかなり汚すのでHelper化してしまいましょう。
  • [追記]商用に限り有料
  • ptengineというアクセス解析サービスで使っていた。
  • 動きが恰好よい。マップもあるしぱねぇ。
  • iphone4だとちと重い。
  • 商用利用不可でライセンス購入が必要。結構高い。

jqplot

amcharts

  • MicroSoftやAmazonも使用していライブラリ。
  • ここで紹介する中でも一番多機能だと思います。
  • デザインもフラットできれいなのでチャート単体でも映えるので、色々なところに使えそうです。

chart.js

  • シンプルで、動きがあって、見ためはcchart.jsより好み。
  • ccchartは自分のやりたいことがほぼできたので、機能的に不満はないけど、こっちがどれだけのことが出来るかチェックしてみよう。
  • HTML5 canvasベースのJSライブラリ。
  • フラットなグラフをかける。

参考

http://qiita.com/hurutoriya/items/727296839a2ec638fdc4
http://www.fwoabw.info/entry/20130924/1380008062

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】footerを固定する方法

今回はfooterを固定して表示する方法について学んで行きます。

<script>
$(function(){
var = $ftr = $('footer');  
if(window.innerHight > $ftr.offset().top + $ftr.outerHight()){
$ftr.attr({'style': 'position:fixed; top:' + (window.innerHeight - $ftr.outerHeight()) +'px;' });
}
});
</script>

解説
まず、var = $ftr = $('footer');でDOM取得後に変数に格納しています。
次にinnerHightで画面全体の高さを取得。
$ftr.offset().topで画面トップからfooterまでの距離を取得します。
$ftr.outerHight()でfooter自身の高さを取得します。
$ftr.attr({'style': 'position:fixed; top:' + (window.innerHeight - $ftr.outerHeight()) +'px;' });
もし、画面全体の高さの方が大きい場合は、styleで付け足す
付け足す時は画面全体-footer自身の高さを引いたpx分だけ付け足す。
→結果的に残り部分が決まるのでfooterが決まる仕組みとなってます。

まとめ
今回はJsで記述してみましたが、ネットで見るとCSSだけでもかけそうなので、今度CSSのみでチャレンジ
してみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonしか触ったことのなかった大学生がReact(Gatsby.js)でWeb開発した話

作ったLP(ランディングページ)

SynQ Remote LP https://www.synq-platform.com/

大学1年の10月から福岡市内のITベンチャーでインターンをしており、そこで自社プロダクトのLP(ランディングページ)開発プロジェクトを任せてもらっています。
今月でインターン開始1年になるので、記念に(?)LP開発までの流れを書いていきたいと思います。

LPを作るまでの自分のスキルについて

インターン開始前は授業でPythonを触ったのみ

インターンを始める前、大学1年前期の講義で「プログラミング演習」という必修科目を取っていました。これは関数を定義するくらいまでのPythonの基礎を学ぶ科目で、昔からパソコンをいじるのが好きだった自分はとても面白い講義だなと思い、これをきっかけにプログラミングを学びつつバイト代わりにもなるインターンを探すことを決意します。

インターン先を見つけ、プロジェクトに配属される

2ヶ月ほど期間を要しましたが、ようやくWantedlyで自分に合ったインターン先を見つけることができました。早速、鉄板加工工場のオートメーション化プロジェクトに配属され、主にPythonベースのDjango(バックエンド)の開発を任せてもらいます。初めの方はわからないことだらけでしたが、3, 4ヶ月経つと一定の成果をあげることができるようになっていきました。

Python以外に手を出してみたくなる

すると、そろそろPython以外の言語にも手を出してみたくなりました。インターン先CTOに相談したところ、ReactでQiitaトップページを模写するという課題をいただきました。2月に合宿免許に行くことになりインターンできない状況にあったので、その期間をReact勉強期間に充てたのち、Qiita模写を1週間くらいで開発しました。(https://github.com/horri1520/qiita-toppage/)
スクリーンショット 2020-09-16 13.23.12.png

LP開発プロジェクトを任せてもらうことに

Qiita模写を通してフロントエンド楽しい!!!もっといろいろ作りたい!!!となっていた自分を見て、前述のCTOがLP開発プロジェクトを任せてくれることになりました。当時僕はフロントエンドの技術スタックについて全然詳しくなかったので、CTOがGatsbyなるものを提案してくれました。ここから、LP開発についての詳細を書いていきたいと思います。

技術スタックについて

フレームワークはGatsby.js

Reactベースの静的サイトジェネレータ、「Gatsby.js」を採用しています。僕のアイデアではないですが(笑
しかし開発を通して、Gatsbyを使ったことによる恩恵を十分に享受することができました。Gatsby製のサイトはなんと言ってもパフォーマンスに長けています。爆速です。
爆速になるカギは静的サイトホスティングサービス「Netlify」と、サイト内の画像を最適化してくれるGatsby専用プラグインgatsby-imageにあると思っています。

Netlifyとgatsby-image

Netlifyはサーバーレスで静的サイトをビルド・ホスティングできるサービスです。主な特徴は、GitHubへのpushをトリガーにサイトのビルド・デプロイが走ることと、その際行われるサーバーサイドレンダリングです。(後者は諸刃の剣な側面があって、、、詳しくは後述します。)
gatsby-imagelazy-loadやサイズ最適化など、画像を軽量化して扱うための機能がひとまとめになっているプラグインです。詳しいことは割愛しますが、GraphQLのクエリを叩けば軽量化された画像ファイルを生成してくれます。
Netlifyとgatsby-imageを組み合わせるとどういうことが起こるのかについて説明します。まず、GitHub上のコードから静的サイトのビルドが走ります。サイト上の各要素はこの段階で描画(レンダリング)され、1枚のページビューにまとめられます。その際サイト上の画像はぼやけた、モザイクに近いようなものとしていったん生成されます。
そしてそのページにアクセスすると、最初はモザイクに近い画像が表示されますが、ページを読み進めていくにつれ、高解像度の画像の読み込みが走り(lazy-load)順次置き換えられていきます。一連の流れを視覚化すると、このような挙動になります。
1.gif

レンダリングがすでに行われているというだけでも十分高速なのですが、さらに重たい原因になる画像まで軽量化してくれるところがGatsbyならではの強みです。

技術スタックまとめ

Gatsby.jsで書いたコード
 ↓push
GitHub
 ↓pushをトリガーにビルド&デプロイ
Netlify

実装してみて

とある日、いつものようにビルドを走らせると…

image.png

ページがぐっちゃぐちゃになっていた。。。

image.png

めっちゃくちゃ萎えました。このスクショ以外にも様々な見た目で崩壊(画像など各要素は表示されるけど位置がはちゃめちゃとか)していましたが、萎えたのでこのスクショしか撮れていないほどです。やはり自分が開発したサイトがバリエーション豊かに崩壊しているのを見るのはへこみますね。しかし、そうも言ってられないので崩壊原因の調査を始めることにしました。

原因調査

いろいろ調べているうちに、同じ現象に悩まされている方々が執筆したいくつかのブログ記事やフォーラムにたどり着きました。おそらく、Material UIのmakeStylesとレスポンシブ対応プラグインのreact-responsiveがサーバーサイドレンダリングに対応していないことが全ての原因であるとのことです。しかし、どのページにも有効な解決策は示されておらず、GitHubのissueに至っては解決していないのに閉じられていました。絶望です。

解決策を求めて

いったん状況を整理するために、コードを書き換えてみたりコメントアウトしてみたりしました。その結果、

  • react-responsiveは完全にアウト。発生条件は定かではないが、動作しない箇所が出てきてしまう。
  • makeStylesはindexページのみの適用だと動作するが、他のページに適用するとそのページのCSSが崩壊してしまう。404ページに至っては、404ページ自体もindexページも共に崩壊してしまった。
  • これらの現象はlocalhostで立てた開発用サーバーでは発生せず、ビルドしてみて初めて異変に気づくのでそもそも気づくのも遅れる上デバッグもしづらい。

という結果が得られました。
もうGatsbyやめようかな…という考えも一瞬頭をよぎりましたが、上述したGatsbyのメリットが大きすぎるため、Gatsbyでも正常に動作するプラグインを探し、それに置き換えることにしました。

公式ドキュメントを読むことの重要性

ユーザーで困っている人々がいるのなら、公式はそれに対しどのような見解を持っているのだろうかと気になり、ここでいったんGatsby公式のドキュメントに立ち返ってみることにしました。すると、GatsbyでのCSSスタイリングをどうするのかについてのページにたどり着き、(https://www.gatsbyjs.com/tutorial/part-two/) styled-componentsが推奨されているということを知りました。(逆にmakeStylesが非推奨とはどこにも書かれていませんでしたが!笑)

このことで、

  • 公式ドキュメントをきっちり読むこと
  • サーバーサイドレンダリング下において、特に画面描画周りのプラグインとは相性があるので事前に対応状況を確認するべきである

この2点の重要性を再認識できました。また、レスポンシブ対応プラグインについては、同様にGatsby公式プラグインライブラリ(https://www.gatsbyjs.com/plugins/) からgatsby-plugin-breakpointsという対応プラグインを見つけ出すことができました。

一連の流れについて振り返ると、発見した時は慌てふためきましたが、今となっては
得られた知見だけでなく問題について調査し、解決に向けて試行錯誤していくプロセスにもとても意義があったと思っています。今後新たな技術に触れる際、何かトラブルが起きてももう怖くなさそうです。

続いて、gatsby-plugin-breakpointsが便利だったので詳しく紹介していきます。

gatsby-plugin-breakpointsの使い方

詳しくはドキュメント(https://www.gatsbyjs.com/plugins/gatsby-plugin-breakpoints/) を参照していただきたいのですが、gatsby-config.js内にbreakPointsを定義すれば各コンポーネントで手軽に使えます。

僕は下のようにbreakPointsを定義しました。

const breakPoints = {
  smartphone: "(max-width: 420px)",
  mobileWide: "(min-width: 421px)",
  wxga: "(min-width: 1280px)",
  mobile: "(max-width: 1023px)",
  pc: "(min-width: 1024px)",
  portrait: '(orientation: portrait)',
}

そして、使用したいファイル上で

import { useBreakpoint } from "gatsby-plugin-breakpoints"

const hoge = () => {
    const breakPoints = useBreakpoint()

    return (
        <>
            {breakPoints.pc
                ?
                    // PC用のコンポーネント
                :
                    null
            }
            {breakPoints.mobile
                ?
                    // モバイル用のコンポーネント
                :
                    null
            }
        </>
    )
}

のように、閲覧しているデバイスの画面サイズに応じてreturnするコンポーネントを切り替えることができます。
breakPointsの中には、

{ pc: true, mobile: false, wxga: true, ... }

このようにconfigで定義したbreakPointsを満たしているかどうかがboolean値で格納されているので、画面サイズに応じた条件処理を自由度高く書くことができました。

1.gif

その結果、CSSのメディアクエリと遜色ないレスポンシブ対応ページをJavaScriptだけで実装することができました。JavaScriptだけでの実装にこだわってよかったなと思っています。

Gatsby.jsでのWeb開発まとめ

  • CSS in JSプラグインはstyled-componentsgatsby-plugin-styled-componentsとセットで使う
  • レスポンシブ対応プラグインは専用のgatsby-plugin-breakpointsを使う
  • Gatsbyのサーバーサイドレンダリングに非対応のプラグインはまともに動作しないので注意!事前によく調べるべき

LPの開発を終えて

Gatsby.jsはサーバーサイドレンダリング起因のCSS崩壊や、有効なプラグインを調べてきてカスタマイズ・チューニングが必要なことなど、調整に手のかかるところがあるという欠点もありますが、それ以上に、gatsby-imageによる画像ファイルの最適化だったり、NetlifyやGitHubとの連携だったり、上で紹介しませんでしたがpagesにJSファイルを置けばルーティングまで勝手にやってくれることだったりと、Webサイト開発において優れた点が多いです。今後静的なWebサイトを開発する時も、今回得た知見をもとにGatsbyで開発したいなと思っています。
そして、インターンの今後のプロジェクトですが、LPと並行して、

  • インターン先のHP https://quando.jp
  • インターン先自社プロダクト SynQ Remote
  • 鉄板加工工場オートメーション化プロジェクト Webアプリ開発(Vue.jsとDjango)
  • 同プロジェクト用CNN(画像認識)モデル

なども任せてもらっているので、フロントエンドだけでなくバックエンド・機械学習など、フルスタックエンジニアを目指して様々な分野の技術に触れていきたいです。

最後に

https://www.wantedly.com/companies/quando
インターン先である株式会社クアンドでは、エンジニア・デザイナーさんを募集しています!
特にフロントエンド・バックエンド・インフラ・モバイルアプリ・UIデザインなどの分野で募集していますが、Webに限らず様々なプロジェクトをやっているので、分野関係なく純粋に技術が大好きな方の応募をお待ちしております!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsで16進数文字列を文字列に変換メモ

Arduinoなどのマイコンボードなどのから情報送るときにたまに使うやつです。

Bufferでシンプルに書く

今のところこれがシンプルな感じです。 Bufferを使うのでNode.js環境のみですが

const string = Buffer.from(hexStr, 'hex').toString('utf-8');

これでOK。

実際に書くときはこんな感じです。

app.js
const hexStr = `48656c6c6f`; //16進数文字列
const string = Buffer.from(hexStr, 'hex').toString('utf-8');

console.log(string);

その他

  • もう少し丁寧に
const buf = Buffer.from(hexStr, 'hex'); //16進数文字列 -> Buffer
const string = buf.toString('utf-8'); //Buffer -> 文字列
  • バイト配列指定
const buf = new Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); //48656c6c6f
const string = buf.toString('utf-8');
  • ブラウザでも使える版
const string = (new TextDecoder).decode(Uint8Array.of(0x48, 0x65, 0x6c, 0x6c, 0x6f)); //48656c6c6f

ちなみに48656c6c6fは?

変換するとstringの値はHelloになります。

$ node app.js
Hello
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ページ遷移後はJSが読まれず、ロードするとJSが読み込まれるという不具合に直面した件

状況

Railsを利用しフリマアプリ開発中に体験したエラーです。商品出品機能実装中、価格入力でイベント発火し出品する商品の手数料を自動計算し表示する機能を実装しました。

自動表示されることを確認後、トップページから商品出品ページのリンクに飛び商品出品機能が動作しているか確認していると、価格を入力しても手数料が表示されないという不具合を発見しました。

コードの記述等のチェックをしていると、いつの間にか手数料の自動表示機能が復活しているという現象に見舞われパニックを起こしました。

解決の糸口

色々やっていて、いつの間にか機能が復活なんてありえないので、コードを1つずつ書き換えたりボタンを1つ押す毎に挙動がどう変化するのか試したところ、どうやらページ遷移した直後のページではJavaScriptが読み込まれず、リンク先に飛んだ後にもう1度ページ更新をするとJavaScriptが読み込まれているということが発覚しました。

window.addEventListener('load',function(){
この中に手数料表示のコード
});

ページ読み込みが全てのJavaScriptのイベント発火に必要なので、ページを飛んだ時にはloadが読み込まれない仕様なのかと考えました。しかし、調べていくと、loadはページ遷移しただけでも読み込まれるイベントであるとのことで、ますますJSが動かないことに混乱することになりました。

解決

結局JavaScriptのコード自体には何も問題はありませんでした。そこで、そもそもJavaScriptがどのように読み込まれているか確認をすることにしました。railsでJavaScriptを読み込む流れとしては以下のような感じです。

application.html.erbの共通のビューのheadタグの中で読み込んでいます。

<head>
  <title>Furima</title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <script type="text/javascript" src="https://js.pay.jp/v1/"></script>
  <%= stylesheet_link_tag 'application', media: 'all'%>
  <%= javascript_pack_tag 'application' %> ←この記述でapplication.jsを読み込んでいます。
</head>

app/javascript/packs/application.jsでrailsで使うJavaScriptをまとめて読み込んでいます。

中略
require("@rails/ujs").start()
require("turbolinks").start() ←不具合の原因(下記で説明)
require("@rails/activestorage").start()
require("channels")
require("../fee.js")
require("../card.js")
中略

読み込んでいるものを1つずつ調べていくと、ようやく原因が判明しました。
不具合の原因はturbolinksをapplication.jsで読み込んでいることでした。turbolinksは大規模な開発等では読み込むJavaScriptが多くなるので、それを効率よく読み込めるようにする為のものみたいです。

しかし、悪い部分もあってページ遷移した直後にloadのイベント発火を読み込んでくれない現象を起こすことがあるようです。

railsではアプリを作成するとapplication.jsでturbolinksを読み込む記述が自動で記述されてしまいます。turbolinksは大規模サイトでJavaScriptがすぐ読まれるようにする為のもので個人でポートフォリオを作るような場合は不要であるので、こちらをコメントアウトして読まれなくすると、ページ遷移後にJavaScriptが起動しないという不具合を解消することができました。

大規模な開発だとデフォルトで読み込んでいるturbolinks等も別のものに書き換えたりして使うこともあるらしいです。

まだまだ初学者で間違えた知識を書いている可能性もあるので、過ちがあれば教えていただけると嬉しいです。

拙い文章であったかと思いますが、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザWebVRで遊ぶ「おにごっこ」をA-frameとp5jsで作ってみた

おにごっこしたい

最近はいろんな技術がすすみ、VR・AR・MRといった言葉も市民権を得てきましたよね。
昔ドラえもんで出てきた秘密道具が「あれこれスマホでできるじゃん」みたいなものだったり。
当時は「ドラえもん的」だと思われていたものがどんどん現実になってきていて、本当に技術の進歩ってすごいなあと思います。
どこでもドアホッシィィィイイイイ。

先日投稿した「光センサで遊ぶ「かくれんぼ」をobnizとp5jsで作ってみた」の中で、ブラウザで遊べるかくれんぼゲームを作りました。
A-frameを使えばサクッとVRが作れるということを知り、VR版おにごっこを作ってみました。
ただ逃げ回るだけも面白くないなあと思ったので、白黒世界でめちゃめちゃ見えにくい世界を作ってみました。

コードはGistに載せてあるので是非実装してみてくださいね。
アプリもNetlifyに載せたので是非遊びに行ってみてください。
難易度がいまいちわからなかったので、3つ作ってみました。

Gistはこちらから
Appは
やさしいモードはこちらから
ふつうモードはこちらから
くらやみモードはこちらから
※VRをお持ちの方、今回はブラウザで操作すること前提で実装してしまったため、VRで操作する仕様になっていない可能性があります(theクソ仕様)。それを踏まえたうえでお楽しみください。

完成デモ

基本ルール

おにごっこと同じく、おにから逃げ続けるゲームです。
おには暗闇に溶け込みそうな真っ黒な箱。
こいつに追いつかれるか、場外に出てしまうとゲームオーバーになります。

黒い箱は近くにプレイヤーを見つけると接近してきます
検知の範囲はそこまで広くないので正面にいる場合は大丈夫だと思いますが、木々からヌッと出てくることもあるので要注意です。
また黒い箱は一日ごとに足が速くなります
3日目ぐらいでプレイヤー同等ぐらいのスピードになるので、うまく先読みをしながら逃げ回ってください。

朝と夜

ゲーム内には時間の概念がありまして夜になるほど空が暗くなってきます
この真っ暗な間は黒い箱は背景に隠れてしまう上に、木々が邪魔で見通しが悪くなります。
この間も黒い箱に近づくと接近してくるので要注意です。

ゲームオーバー

タッチされる・場外に出ると記録が表示されます。

ちなみに(余談)

今回初めてのVR制作でして、VRのヘッドセットを使うことも初めて
今回はOculus Goを一からセットアップしてやってみました。
動画も取ったんですが、なぜか起動ができなくなってしまってデータが抜けず断念。。。
DSC_0096.JPG
テンションの上がるぼく

基本的な操作はブラウザと同じく、手元のコントローラーのクリックでどうにかなりそう。
クリックする必要が無く周りが見渡せるので、もしかしたらこちらの方が簡単かもしれません。
ただ今回「ゲームオーバーしたらRボタンを押してね」仕様にしているので、ゲームオーバーのたびにURLを叩く必要があるのが難点。

この辺りはヘッドセットの種類に応じて実装を変える必要があるんですかね?
今回はOculus Go自体初めてだったので、もう少し突き詰めてみたいところですね。

実装こまごま

基本アーキテクチャ

今回は
・index.html (画面表示用)
・sketch.js (常に処理し続ける内容の記述)
・components.js (基本オブジェクトのクラス・メソッドを記述)
の3つで構成されています。
index.htmlはほとんどライブラリの呼び出しの記述のみで、メインはsketch.jsになります。

A-frameはa-skyやa-entityのようなタグを記述することで3Dオブジェクトをラクラク生成を可能にしてくれる便利なライブラリです。
今回はcreateElementなどを駆使しながら、a-sceneタグの子要素としてこれらのオブジェクトを入れ込んでいます。
詳しくは公式ドキュメントA-frameまわりのQIita記事をご覧になりながら、是非ハンズオンしてみてくださいね。

<html>
    <head>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.1/p5.js"></script>
        <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
        <script src="components.js"></script>
    </head>

    <script src="sketch.js">
    </script>

    <body>
    <a-scene id="VRScene">
      ここにオブジェクトのタグが入りまくるイメージ
    </a-scene>
 </body>
</html>

オブジェクトの生成処理

今回はこちらのサイトをかなり参考にさせていただきました。
こちらのサイトにあるコードを読み解いて、実装に利用した形になります。
p5jsのお作法については先日の記事を見つつ実装しています。
今回は備忘録的にA-frameに関するお作法をメモしておきます。

p5jsではブラウザが読み込まれると同時にsetup()が実行されます。
setup()ではまずworldクラスという各オブジェクトを管理するためのクラスを生成します。
worldクラスで必要そうなメソッドはcomponents.jsに実装されています。

//sketch.js
function setup() {
    noCanvas();
    world = new World('VRScene');

        //
        //以下省略
        //
}

//components.js
function World(id) {
    console.log("A-FrameP5 v0.1 (Craig Kapp, 11/8/2016)");

    if (id == undefined) {
        id = "VRScene";
    }
    this.scene = document.getElementById(id);

    this.flying = false;
    this.setFlying = function(v) {
        this.flying = v;
        this.camera.setWASD(v);
    }
    this.getFlying = function() {
        return this.flying;
    }

    this.camera = new Camera();
    this.scene.appendChild(this.camera.holder);

    this.add = function(entity) {
        this.scene.appendChild(entity.tag);         
    }
    this.remove = function(entity) {
        this.scene.removeChild(entity.tag);         
    }

    this.removeall = function(){
        while(this.scene.firstChild){
            this.scene.removeChild(this.scene.firstChild);
        }
    }

       //
       //その他様々な処理を定義
       //
}

このworldクラスへオブジェクトをどんどん追加していきます。
今回の敵役である黒い箱に着目すると、

//sketch.js
//setup()の中
    enemy = new Box({x:x, y:0.5, z:z,
                     width:1, height:1, depth:1, 
                     red:0, green:0, blue:0,
                    });
    world.add(enemy);


//components.js
function Box(opts) {
    // store desired options
    setEntityOptions(opts, this);

    // store what kind of primitive shape this entity is
    this.prim = 'box';

    // setup geometry parameters
    if (!('width' in opts))  {
        opts.width = 1;
    }
    if (!('depth' in opts))  {
        opts.depth = 1;
    }
    if (!('height' in opts)) {
        opts.height = 1;
    }
    this.width  = opts.width;
    this.height = opts.height;
    this.depth  = opts.depth;

    // set geometry
    setGeometry(this);

    // set material
    processMaterial(this);
    setMaterial(this);

    // set scale
    setScale(this.opts, this);

    // set position
    setPosition(this.opts, this);

    // set rotation
    setRotation(this.opts, this);

    // set visibility   
    setVisibility(this.opts, this);

    // set click handler
    setClickHandler(this);

    // init common setters / getters
    initializerSettersAndGetters(this);

}

components.js中のsetEntityOptions()でタグを生成し、setGeometry()・setMaterial()・setScale()などによって属性情報の挿入処理が走るようです。
setEntityOptions()のcreateElement()でタグが生成します。

function setEntityOptions(opts, entity) {
    // store desired options
    if (opts == undefined) {
        opts = {};
    }
    entity.opts = opts;

    // create a tag for this box
    entity.tag = document.createElement('a-entity');

    // setup a "children" array
    entity.children = [];
}

その後の処理で属性情報が挿入されていきます。
setGeometryを代表例として示しますが、オブジェクトの形状に応じてタグの属性情報が変わるのでelse if(caseのほうが良いんでしょうか?)の嵐ですね。
ここでsetAttributeすることでタグへ属性情報を挿入することができます。

function setGeometry(entity) {
    if (entity.prim == 'sphere') {
        entity.tag.setAttribute('geometry', 'primitive: sphere; radius: ' + entity.radius + '; segmentsWidth: ' + entity.segmentsWidth + '; segmentsHeight: ' + entity.segmentsHeight + '; phiStart: ' + entity.phiStart + '; phiLength: ' + entity.phiLength + '; thetaStart: ' + entity.thetaStart + '; thetaLength: ' + entity.thetaLength);             
    }
    else if (entity.prim == 'circle') {
        entity.tag.setAttribute('geometry', 'primitive: circle; radius: ' + entity.radius + '; segments: ' + entity.segments + '; thetaStart: ' + entity.thetaStart + '; thetaLength: ' + entity.thetaLength);              
    }
    else if (entity.prim == 'ring') {
        entity.tag.setAttribute('geometry', 'primitive: ring; radiusInner: ' + entity.radiusInner + '; radiusOuter: ' + entity.radiusOuter + '; segmentsTheta: ' + entity.segmentsTheta + '; segmentsPhi: ' + entity.segmentsPhi + '; thetaStart: ' + entity.thetaStart + '; thetaLength: ' + entity.thetaLength);              
    }
    else if (entity.prim == 'cone') {
        entity.tag.setAttribute('geometry', 'primitive: cone; height: ' + entity.height + '; openEnded: ' + entity.openEnded + '; radiusBottom: ' + entity.radiusBottom + '; radiusTop: ' + entity.radiusTop + '; segmentsRadial: ' + entity.segmentsRadial + '; segmentsHeight: ' + entity.segmentsHeight + '; thetaStart: ' + entity.thetaStart + '; thetaLength: ' + entity.thetaLength);            }
    else if (entity.prim == 'torus') {
        entity.tag.setAttribute('geometry', 'primitive: torus; radius: ' + entity.radius + '; radiusTubular: ' + entity.radiusTubular + '; segmentsRadial: ' + entity.segmentsRadial + '; segmentsTubular: ' + entity.segmentsTubular + '; arc: ' + entity.arc);            
    }
    else if (entity.prim == 'torusKnot') {
        entity.tag.setAttribute('geometry', 'primitive: torusKnot; radius: ' + entity.radius + '; radiusTubular: ' + entity.radiusTubular + '; segmentsRadial: ' + entity.segmentsRadial + '; segmentsTubular: ' + entity.segmentsTubular + '; p: ' + entity.p + '; q: ' + entity.q);           
    }
    else if (entity.prim == 'cylinder') {
        entity.tag.setAttribute('geometry', 'primitive: cylinder; radius: ' + entity.radius + '; height: ' + entity.height + '; openEnded: ' + entity.openEnded + '; segmentsRadial: ' + entity.segmentsRadial + '; segmentsHeight: ' + entity.segmentsHeight + '; thetaStart: ' + entity.thetaStart + '; thetaLength: ' + entity.thetaLength);         }
    else if (entity.prim == 'box') {
        entity.tag.setAttribute('geometry', 'primitive: box; depth: ' + entity.depth + '; height: ' + entity.height + '; width: ' + entity.width);  
    }
    else if (entity.prim == 'plane') {
        entity.tag.setAttribute('geometry', 'primitive: plane; height: ' + entity.height + '; width: ' + entity.width);
    }
    else if (entity.prim == 'octahedron' || entity.prim == 'tetrahedron' || entity.prim == 'dodecahedron') {
        entity.tag.setAttribute('geometry', 'primitive: ' + entity.prim + '; radius: ' + entity.radius);
    }
}

setGeometryだとかsetMaterialだとか、それぞれがどんな役割をしているのかについてはまだまだ理解できていません。。。
A-frameのEntity-Component-Systemあたりが関係していそうなんですがいまいちわからず。。。
ちょっとずつ勉強していこうと思います。

おにごっこの処理

一通りオブジェクトの生成がすんだら、残りはオブジェクトをどう動かすかの処理になります。
p5jsのお作法ではdraw()が常に実行され続けるので、この部分におにごっこのロジックを書いていきます。
細かいロジックについては先日の記事を参考にしてみてください。

コード全体はこちらからご覧いただけますので、是非是非実装してみてください!
参考にしたサイトの方もこちらからご覧ください。

今後やりたいこと

A-frame面白い。。。楽しい。。。
みんなが作った3Dモデルの共有サイトもあるらしく、そこから良いモデルをインポートすることもできる様子ユメガヒロガルゥゥゥウウ
VR周りはもっと勉強していきたい。

最後までご覧いただきありがとうございました!!
LGTMつけていただけると励みになります、よろしくお願いします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

簡易LISP処理系の実装例【各言語版まとめ】

この記事は,様々なプログラミング言語でS式入出力および基本リスト処理を実装した上で,John McCarthy氏の原初のLISPインタプリタ記述をPaul Graham氏がCommon Lispで実装した"McCarthy's Original Lisp"jmc.lisp)について,各言語向けに移植・動作確認してみた記述例のリンク集および共通解説をまとめたものです.

なお,複数の種類の括弧を用いた記述の簡易パーサ,基本リスト処理のみを実装した例をまとめた記事もあり,こちらの記述から抜粋・修正して組み込んでいる場合もあります.

この記事の方が新しく,少しずつ整理していますが,各リンク先記事との整合性が合っていなかったり,記述や説明が重複していたりする箇所があるかもしれません.御了解いただけますと幸いです.

実装例の趣旨

LISP系言語については,開発当初より『LISP自身でそのLISP処理系を記述する』という,超循環評価器(meta-circular evaluator)としての実装が行われています.最低限の機能をもったLISP処理系であればそのような実装は可能であり,しかも,その評価器の仕組みはとても簡単です.McCarthy's Original Lispの他,SICP 4.1など,Webでも多くの記述例が公開されています.

これらを参照すれば,LISP系以外の他のプログラミング言語でも,超循環評価器としての性質をもつ同じLISP処理系が容易に実装でき,言語処理系実装の入門用として最適…のはずなのですが,LISP処理系ならば標準で装備している,字句・構文を規定する『S式』の入出力処理,および,S式に基づく基本リスト処理(car,cdr,cons,eq,atom)の実装の方が開発言語ごとの手間が圧倒的にかかり,それが敷居になっているところがあります.

そこで,各プログラミング言語で簡単なS式入出力および基本リスト処理の実装例を別途作成し,"McCarthy's Original Lisp"を可能な限りそのまま移植・動作確認することで,言語処理系実装の最初の敷居を下げてみよう,というのが,今回の各実装例の趣旨です.

処理系の概要

次のサンプルの通り,名前付けや関数定義の記述方法がなく,ひとつのまとまったS式のみの処理を行うものですが,ダイナミックスコープということもあり,lambda式を用いて再帰関数を定義して利用することも可能です(SchemeのletrecやCommon Lispのlabelsなどの代わり).

(car (cdr '(10 20 30)))
=> 20

((lambda (x) (car (cdr x))) '(abc def ghi))
=> def

((lambda (f x y) (f x (f y '()))) 'cons '10 '20)
=> (10 20)

((lambda (f x y) (f x (f y '())))
 '(lambda (x y) (cons x (cons y '())))
 '10 '20)
=> (10 (20 ()))

((lambda (assoc k v) (assoc k v))
 '(lambda (k v)
    (cond ((eq v '()) nil)
          ((eq (car (car v)) k)
           (car v))
          ('t (assoc k (cdr v)))))
 'Orange
 '((Apple . 120) (Orange . 210) (Lemmon . 180)))
=> (Orange . 210)

評価器本体の実装内容は次の通り.

  • "McCarthy's Original Lisp"を基にした,超循環評価器としての性質をもつLISP処理系
  • 数字を含むアトムは全てシンボルとし,変数の値とする場合はquote')を使用
  • 構文としてquoteの他,condlambdaが使用可能
  • 組込関数:cons car cdr eq atom(内部でコンスセルを作成)
  • 真偽値はt(真),および,nil(偽)=空リスト(+内部用記号)
  • 評価器実装専用として,caarassocなどのユーティリティ関数を定義
  • エラーチェックなし,モジュール化なし,ガーベジコレクションなし

また,S式入出力およびリスト処理実装の構成は次の通り.

  • 基本リスト処理:cons car cdr eq atom
  • S式字句解析:1行の文字列から( ) 'を字句として,空白を区切り記号として,文字列配列を生成
  • S式構文解析:( )の括りをconsでリスト化,'(quote ...)を挿入,.はコンスセルを生成
  • S式出力:ドット対簡略その他の表現に基づくリスト構造などを表示,または,文字列として出力
  • REP (no Loop):文字列1行読み込み→S式抽象構文木生成→評価→S式出力をまとめた関数を定義

評価器の解説

s_evalの処理内容を箇条書きにすると,次のようになります.

  • 引数としてS式eと環境変数をとる.
  • eが真偽値を示す文字列ならば所定の真偽値を返す.
  • eがリスト構造ではないならば束縛変数とみなし,対応する値を環境変数から取得して返す.
  • eがリスト構造であり,先頭の要素e1がリスト構造ではないならば,次の処理を行う.
    • e1quoteならば,eの2番目の要素をそのまま値として返す.
    • e1atom eq car cdr consならば,引数要素を評価した後に関数適用を行い,その結果を返す.
    • e1condならば,条件式と処理を組にしたリストをevconに渡し,その結果を返す.
    • それ以外の場合は,e1をlambda式の束縛変数とみなし,対応するlambda式を環境変数から取得してeの先頭要素として置き換え,あらためて評価した結果を返す.
  • e1もリスト構造であり先頭の要素がlambdaならば,lambda式の値適用とみなし,次の処理を行う.
    • 適用する値要素をリストにしたものをevlisに渡し,それぞれの要素が評価された結果を再度リストとして受け取る.
    • lambda式の各引数と評価済の各値要素を対応付けたリストを作り,環境変数に追加する.
    • lambda式の処理本体を,更新後の環境変数を用いて評価した結果を返す.
  • eが上記以外の構成の場合は,エラーとして()を返す.

肝となるのは,lambda式を別のlambda式の引数に束縛した後の,lambda式の値適用,たとえば,

(s_eval '((lambda (f) (f '(a b c))) '(lambda (x) (cdr x))) '()))
=> (s_eval '(f '(a b c)) '((f (lambda (x) (cdr x))) . ()))
=> (s_eval '((lambda (x) (cdr x)) '(a b c)) '((f (lambda (x) (cdr x))) . ()))
=> (s_eval '(cdr x) '((x (a b c)) (f (lambda (x) (cdr x))) . ()))
=> (cdr (s_eval 'x '((x (a b c)) (f (lambda (x) (cdr x))) . ())))
=> (cdr '(a b c))
=> (b c)

のように実行されていく処理でしょうか.環境変数内でlambda式に名前が付くことによって,その名前で自分自身を呼び出す再帰処理が定義可能です.

環境変数は,引数持ち回りとはいえ,ひとつのみです.ですのでダイナミックスコープとなるのですが,今回の評価器は,lambda式のみのS式はエラーとし(というよりも,真偽値およびクォートされた記述以外は値としてそのまま返さない),lambda式の処理本体としてlambda式を記述する,すなわち,lambda式を返すlambda式は処理できません.高階関数機能としては,いわゆる第二級オブジェクト相当となります.

実のところ,真偽値のように,lambda式のみの場合はそのまま返すこともできなくはないのですが,lambda式内にローカル環境変数を保持する,クロージャ機能を実装したレキシカルスコープとしないと,名前衝突(funarg)の問題が起きます.そして,レキシカルスコープでは別のlambda式内のローカル環境変数の値を適用できませんから,再帰処理定義のためには,グローバルな環境変数に変数束縛を直接追加する構文や,Yコンビネータのような不動点コンビネータが必要となってきます.

備考

記事に関する補足

  • 趣旨としては他にも,簡易処理系とはいえ,ホスト言語の様々な機能を活用しないと実装できないことから,その言語を本格的に学ぶための題材としても適切な種類と規模,というのもあります.とりあえず,Haskellのリストモナドの妙なクセは一通りわかった(えっ).

  • 実行サンプルのScheme版,Common Lisp版はそれぞれ次の通り.

sample.scm
(car (cdr '(10 20 30)))
=> 20

((lambda (x) (car (cdr x))) '(abc def ghi))
=> def

((lambda (f x y) (f x (f y '()))) cons '10 '20)    ; 引数として渡された関数名はクォートする必要がない
=> (10 20)

((lambda (f x y) (f x (f y '())))
 (lambda (x y) (cons x (cons y '())))    ; 引数として渡されたlambda式もクォートする必要がない
 '10 '20)
=> (10 (20 ()))

(letrec ((assoc_ (lambda (k v)
                      (cond ((eq? v '()) '())
                            ((eq? (car (car v)) k)
                             (car v))
                            (else (assoc_ k (cdr v)))))))
  (assoc_ 'Orange
          '((Apple . 120) (Orange . 210) (Lemmon . 180))))
=> (Orange . 210)
sample.lsp
(car (cdr '(10 20 30)))
=> 20

((lambda (x) (car (cdr x))) '(abc def ghi))
=> DEF    ; シンボルとしてのアルファベット表示は全て大文字となる

((lambda (f x y) (funcall f x (funcall f y '()))) 'cons '10 '20)    ; 引数として渡された関数はfuncallを用いて実行
=> (10 20)

((lambda (f x y) (funcall f x (funcall f y '())))    ; 引数として渡されたlambda式はfuncallを用いて実行
 (lambda (x y) (cons x (cons y '())))    ; lambda式はクォートする必要がない
 '10 '20)
=> (10 (20 NIL))

(labels ((assoc_ (k v)
           (cond ((eq v '()) '())
                 ((eq (car (car v)) k)
                  (car v))
                 (t (assoc_ k (cdr v))))))
  (assoc_ 'Orange
          '((Apple . 120) (Orange . 210) (Lemmon . 180))))
=> (ORANGE . 210)

更新履歴

2020-09-16:評価器の解説を追加
2020-09-16:初版公開

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactのFunctional ComponentとClass Componentの特徴

Functional Componentの特徴

・ES6のアロー関数で記述する
・stateを持たない
(※2019.02.06に関数コンポーネントにステートを持たせられるAPIが出ている↓)
https://ja.reactjs.org/docs/hooks-state.html

・propsを引数に受け取る
・JSXをreturnする
(※主に使用されているのが、Functional Component)

Class Componentの特徴

・Classを宣言及びReact Componentを継承
・constructorで初期化、propsを引数に受け取る
・renderメソッド内で、JSXをreturnし、引数(props)を受け取るには、thisが必要
・ライフサイクルやStateを持つことができる
(※基本、使用するのはClass Componentではなく、Functional Componentを推奨)

Stateが肥大化した場合の管理の問題

① stateを多くの場所で使用する→Reduxのstoreで管理
② stateを特定の少数の場所で使用→Class Componentで管理

基本的には上記の軸として考えてみると良いかも。

Functional Componentのコード例

import React from 'react';

const Hoge = (props) => {
  return (
   <div>
    <h1>{props.title}</h1>
   </div>
  );
};

Class Componentのコード例

import React from 'react';

Class Hoge extends from React.Component{
  constructor(props){
    super(props);
  }
  render(){
    return(
      <div>
       <h1>{this.props.title}</h1>
      </div>
    );
  }
}

いずれも、最後に

export

を記述し、

export 例)↓

export default Hoge;

さらに、index.js側で

import

import 例)↓

import Hoge from './Hoge';



ここは違う、ここはこうした方が?
等々ございましたら、ご指摘いただけますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS学習その④】プリミティブ型とオブジェクト ~データ型~

JS学習シリーズの目的

このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)

データ型とは

「文字列、数値などの異なる値の型をデータ型という」
         ↓
JavaScript(ECMScript)には8つの型が存在する
(プリミティブ型が7つ + オブジェクト(プリミティブ型以外))

プリミティブ型

プリミティブ型は次の7つです

  • String
  • Number
  • Boolean
  • Undefined
  • Null
  • Symbol
  • BigInt

さらに詳しくプリミティブ型

  • 変数にはが格納される。
  • 一度作成するとその値を変更することはできない。➡この性質をimmutable(不変という意味)という
main.js
    let a = 'Hello';
    a = 'Bye';

上記のコードは、変数aの値が変更しているように見えますが、実際は

1.メモリ空間のどこかに'Hello'という文字列が格納される
2.変数aが'Hello'への参照先を保持する
3.メモリ空間のどこかに'Bye'という文字列が格納される
4.変数aの参照先が'Bye'という文字列に変更される

↑の流れになっています。

オブジェクト

Object
※プリミティブ型以外の全てがObjectと呼ばれる
のちほど解説しますが、「オブジェクトは参照先を名前付きで管理する入れ物」と理解してください

さらに詳しくオブジェクト

  • 変数には参照が格納される。
  • 値を変更することができる。➡この性質をmutable(可変という意味)という
main.js
let a = {
    prop: 'Hello'
}

上記のコードは、
1.変数aに{...}(オブジェクト)への参照先が保持される
2.1.は{prop}への参照先を保持している
3.2.のpropは'Hello'という文字列への参照先を保持している

↑の流れになっています。

まとめ

いかがでしたでしょうか。
データ型には大きく分けてプリミティブ型とオブジェクトがあり、それぞれデータの扱いが違います。
しっかり理解しておきましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習とA-frameの連動方法

A-frameで3Dが簡単につくれることを知った

「3Dなんて僕にはまだ無理無理」

そんな風に思っていたのですが、
初心者でも、簡単に、WEB上で3Dをつくれることを、つい先日知りました。

それがA-frame サイトはこちら

WEBサイト上にこんなものを簡単に表示させることができます。

image10.png

じゃあ、この物体動かしてみよう

それぞれのものを動かすのも簡単です。animationを追加すれば、すぐに動かせます。

<a-box position="-1 1.6 -5" animation="property: position; to: 1 8 -10; dur: 2000; easing: linear; loop: true" color="tomato"></a-box>

こんなものを一行加えるだけで、animationが動きます。

じゃあ、teachable Machineと連動させて動かすには?

if文の中にif文書いたり複雑なことをやろうとしてなかなかうまくいかなかったんですが、
関数をつくることが、なんとかできました。

const imageModelURL = 'teachableMachineから取得したURL';

            async function main() {
                // カメラからの映像取得
                const stream = await navigator.mediaDevices.getUserMedia({
                    audio: false,
                    video: true,
                });
                // html内のidがvideoのdomを取得
                const video = document.getElementById('video');
                // videoにカメラ映像を適用
                video.srcObject = stream;

                // 自作モデルのロード
                classifier = ml5.imageClassifier(
                    imageModelURL + 'model.json',
                    video,
                    modelLoaded
                );
                // モデルのロード完了時に実行される
                function modelLoaded() {
                    console.log('Model Loaded!');
                }
                // 繰り返し検出処理
                classifier.classify(onDetect);
                function onDetect(err, results) {
                    console.log(results);
                    if (results[0]) {
                        console.log(results[0].label);
                        if (results[0].label === '動画から取得した情報') {
                            //  "取得情報"を検出すると、上にanimatinが動く関数
                            move();
                        }
                    }
                    classifier.classify(onDetect);   
                }
                //  対象とする物体にanimationを追加。
                function move() {
                    cylinder.setAttribute('animation', 'property: position; to: 0 5 -3; dur: 2000; easing: linear; loop: true');
                }
            }
            main();

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsの特徴

プログラミング勉強日記

2020年9月16日
JSには色々なフレームワークがあって、開発の用途によって使用するフレームワークが違うと思うので、まとめてみようと思う。ReactについてAngularについてVue.jsについてNext.jsについてRiot.jsについてまとめたので、今回はNode.jsについてまとめる。

Node.jsとは

 Node.js(読み方:ノードジェーエス)は、2009年に作成されたGoogle Chromeのために開発されたもので、JSアプリケーションのプラットフォーム。なので、ライブラリでもフレームワークでもない。
 サーバーサイドJavaScriptで、PHPやRuby、Python、Javaと同様にサーバで動作を行う。軽量で効率よく作業できるので人気のJSライブラリ。

特徴

  • 大規模開発に向いている(大量接続を同時処理できる)
  • C10K問題を解決できる
  • フロントエンドのJSでも処理上の互換性がない

大規模開発に向いている(大量接続を同時処理できる)

 軽量であるので、リアルタイムで複数人が使用する場合でも動作がもたつかない。Webアプリやスマホアプリの作成もNode.jsはしやすく、複数人が同時に接続する場合でも処理のパフォーマンスは落ちにくい。
 つまり、多くのアクセスがあるアプリに向いている。

C10K問題を解決する

 サーバへの接続台数が1万台以上になると処理が遅くなってしまうのがC10K問題であり、この問題をNode.jsを使うだけで解決できるので、技術液なことに時間やコストを割かなくていい。

フロントエンドのJSでも処理上の互換性がない

 Node.jsとフロントエンドのJavaScriptには、同じJSでも処理上の互換性がない。ただ、JSなのでプログラミング言語の基礎的な書き方や知識は活かせる。

参考文献

「.js」選びに迷った時に役立つ!人気のJavaScriptライブラリ&フレームワークまとめ!
初めてでもわかる!Node.jsの特徴やできることとは?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「async」「await」なんだこれ? ついでにforEachの性質について

Array.prototype.forEach() - JavaScript | MDN

forEach は同期関数を期待する

のコード例を見ると

let ratings = [5, 4, 5];
let sum = 0;

let sumFunction = async function (a, b)
{
  return a + b
}

ratings.forEach(async function(rating) {
  sum = await sumFunction(sum, rating)
})

console.log(sum)
// 本来期待される出力: 14
// 実際の出力: 0

関数の前にawaitという記述があったり、関数を定義する前にasyncが付いていて意味がわからなかった。

「async」とは

そもそも「async」とは「Async Function」を定義するためのキーワード

前提として、ES2017で非同期処理の関数を定義するための構文である「Async Function」が導入されていたらしい。Async Functionはpromiseインスタンスを返す関数を定義する構文。
(参考:非同期処理:コールバック/Promise/Async Function · JavaScript Primer)

「async」の使い方

使い方は関数を定義する前に付けるだけ。これだけでpromiseインスタンスを返す非同期処理の関数を定義することができるようになる。

async function testAsync() {
    return "Qiita";
}
// testAsync関数はPromiseを返す
testAsync().then(value => {
    console.log(value); // => "Qiita"
});

/*ちなみにasyncを外すと「TypeError: doAsync(...).then is not a function」。
Promiseインスタンスが返されてないから当然thenメソッドは使えなくなる*/

function testAsync() {
    return "Qiita";
}

testAsync().then(value => {
    console.log(value); // => "TypeError: doAsync(...).then is not a function"
});

「await」とは

point
1.promise処理が終わるまで待ってくれる
2.「async」をつけた関数の中で使う

メリット

非同期処理を同期処理のような見た目で書けるようになる。

「await」、「async」を実際に使ってみる

ここでは、引数で与えられたpathを最終的に配列にして出力するといった処理を行うよう実装する。

function dummyFetch(path) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (path.startsWith("/resource")) {//引数の文字列が"/resource"で始まるかで判定
        resolve({ body: `Response body of ${path}` });
      } else {
        reject(new Error("NOT FOUND"));
      }
    }, 1000 * Math.random())
  });
}

async function fetchAB() {
  const result = [];
  const responseA = await dummyFetch("/resource/A"); //待て!
  // 次の行はdummyFetchcの非同期処理が完了されるまで実行されない
  result.push(responseA.body);
  const responseB = await dummyFetch("/resource/B"); //待て!
  // 次の行はdummyFetchの非同期処理が完了されるまで実行されない
  result.push(responseB.body);
  return result;
}

//リソースを取得して出力する
fetchAB().then((results) => {
  console.log(results);
});

「forEach」の性質を再確認。

「await」、「async」の正体がなんとなく掴めたところで最初のコード例に戻り、
forEachの性質を再確認してみます。

let ratings = [5, 4, 5];
let sum = 0;

let sumFunction = async function (a, b) //Promiseを返す
{
  return a + b
}

ratings.forEach(async function(rating) {
  sum = await sumFunction(sum, rating)
  /*
    awaitの右辺にあるPromiseインスタンスの状態が変わる
    (≒sumFunctionの処理が終わる)のを、forEachは待ってくれない
  */
})

console.log(sum)
// 本来期待される出力: 14
// 実際の出力: 0

これは本来期待される出力結果は14だが、実際は0になってしまう。
原因はforEachはプロミス(非同期処理)を待ってくれない。せっかちなんですね〜。
今回の例で言うと、「sumFunction」を待ってくれない。

デバックして確認してみると、raitingの引数は「5→4→5」と最後まで渡されているが、足し算の処理は行われていない
だから結局、sumは初期値で設定した0のままになっている。

まだ完全に非同期処理の概念だったりが分かったようなわかんないような感じなので(多分全然分かってない)
今後の学習でさらに知識を増やしていきたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「async」「await」なんだこれ? ついでにforEachの性質について(自分用メモ)

Array.prototype.forEach() - JavaScript | MDN

forEach は同期関数を期待する

のコード例を見ると

let ratings = [5, 4, 5];
let sum = 0;

let sumFunction = async function (a, b)
{
  return a + b
}

ratings.forEach(async function(rating) {
  sum = await sumFunction(sum, rating)
})

console.log(sum)
// 本来期待される出力: 14
// 実際の出力: 0

関数の前にawaitという記述があったり、関数を定義する前にasyncが付いていて意味がわからなかった。

「async」とは

そもそも「async」とは「Async Function」を定義するためのキーワード

前提として、ES2017で非同期処理の関数を定義するための構文である「Async Function」が導入されていたらしい。Async Functionはpromiseインスタンスを返す関数を定義する構文。
(参考:非同期処理:コールバック/Promise/Async Function · JavaScript Primer)

「async」の使い方

使い方は関数を定義する前に付けるだけ。これだけでpromiseインスタンスを返す非同期処理の関数を定義することができるようになる。

async function testAsync() {
    return "Qiita";
}
// testAsync関数はPromiseを返す
testAsync().then(value => {
    console.log(value); // => "Qiita"
});

/*ちなみにasyncを外すと「TypeError: doAsync(...).then is not a function」。
Promiseインスタンスが返されてないから当然thenメソッドは使えなくなる*/

function testAsync() {
    return "Qiita";
}

testAsync().then(value => {
    console.log(value); // => "TypeError: doAsync(...).then is not a function"
});

「await」とは

point
1.promise処理が終わるまで待ってくれる
2.「async」をつけた関数の中で使う

メリット

非同期処理を同期処理のような見た目で書けるようになる。

「await」、「async」を実際に使ってみる

ここでは、引数で与えられたpathを最終的に配列にして出力するといった処理を行うよう実装する。

function dummyFetch(path) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (path.startsWith("/resource")) {//引数の文字列が"/resource"で始まるかで判定
        resolve({ body: `Response body of ${path}` });
      } else {
        reject(new Error("NOT FOUND"));
      }
    }, 1000 * Math.random())
  });
}

async function fetchAB() {
  const result = [];
  const responseA = await dummyFetch("/resource/A"); //待て!
  // 次の行はdummyFetchcの非同期処理が完了されるまで実行されない
  result.push(responseA.body);
  const responseB = await dummyFetch("/resource/B"); //待て!
  // 次の行はdummyFetchの非同期処理が完了されるまで実行されない
  result.push(responseB.body);
  return result;
}

//リソースを取得して出力する
fetchAB().then((results) => {
  console.log(results);
});

「forEach」の性質を再確認。

「await」、「async」の正体がなんとなく掴めたところで最初のコード例に戻り、
forEachの性質を再確認してみます。

let ratings = [5, 4, 5];
let sum = 0;

let sumFunction = async function (a, b) //Promiseを返す
{
  return a + b
}

ratings.forEach(async function(rating) {
  sum = await sumFunction(sum, rating)
  /*
    awaitの右辺にあるPromiseインスタンスの状態が変わる
    (≒sumFunctionの処理が終わる)のを、forEachは待ってくれない
  */
})

console.log(sum)
// 本来期待される出力: 14
// 実際の出力: 0

これは本来期待される出力結果は14だが、実際は0になってしまう。
原因はforEachはプロミス(非同期処理)を待ってくれない。せっかちなんですね〜。
今回の例で言うと、「sumFunction」を待ってくれない。

デバックして確認してみると、raitingの引数は「5→4→5」と最後まで渡されているが、足し算の処理は行われていない
だから結局、sumは初期値で設定した0のままになっている。

まだ完全に非同期処理の概念だったりが分かったようなわかんないような感じなので(多分全然分かってない)
今後の学習でさらに知識を増やしていきたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がCodePenのサンプルを駆使したら、マジシャンの兄のプロフィールページを良い感じに作れた話。

私の兄はマジシャンです

いきなりですが、私の兄は「デビッドちんすこう」という名前で沖縄スタイルのマジシャンをしております。
沖縄出身ですが、現在は大阪を中心に活動中!
image.png

マジシャンのイメージからは程遠い風貌。
そして、マジシャンなのにオリジナルソングを5曲ほど作詞作曲。
(各配信サービスでリリースしてるので検索してみてね♪)

単独ライブでは、客席も一緒になってタオルをぶんぶん振り回す新スタイルのマジシャンです。

HP無い問題

そんな自慢の兄なんですが、いかんせん公式HPが無い。
(私が作れと言われてたけど、忘れてた。)

でも色んなメディアで発信しているので、情報が散漫としているのが課題でした。

こうした課題を解決できないかと思い、
これまで学んできた技術で兄のプロフィールページ的なものを作れないか挑戦してみました!

いざ作成開始!

CodePenでサンプル探し

意気揚々と作成開始したものの、UI作るの超苦手!きゃ~!
ここはネットに転がっているサンプルを駆使しよう!ということで、
CodePenで良い感じのサンプル探しから始めました。

CodePen は、ブラウザ上で HTML・CSS・JavaScript のコードを記述することができ、リアルタイムで表示を確認しながら開発ができるサービスです!

Image from Gyazo

このように色んなサンプルが公開されていて、初心者にはとても参考になります!

そして今回、私はこちらのサンプルを使うことにしました!
Image from Gyazo

このサンプルの写真や色を少し変更すると・・・
じゃじゃ~~ん!
Image from Gyazo
簡単に良い感じのページが出来上がりました!

SNSボタンもお兄ちゃん用にカスタマイズしました!
参考:初心者でも、これを読めばOK!簡単にSNSボタンを作れる方法!【Bootstrap/HTML/CSS】

YouTubeを埋め込む①~フレーム作成~

兄が最近力を入れているのは、そう!YouTube!!!

なので、どうしてもYouTubeの動画を埋め込みたい。
まずはhtmlにフレームを作成しました。

        <div style="text-align: center;">    
            <iframe 
             width="240" height="180" 
             src=""
             frameborder="0" allow="accelerometer; autoplay; 
             encrypted-media; gyroscope; picture-in-picture" 
             allowfullscreen></iframe>
        </div>

これでOKかな。どれどれ・・・
Image from Gyazo
あれ・・・???
スクロールできない・・・!?

フレームの上部だけが、虚しく「こんにちは」状態です。

調べてみたところ、原因はCSSにありました。

  overflow:hidden;

overflowがhiddenになっていると、ページの大きさを超えたところは非表示になるそうです。

  overflow:hidden;
  overflow-y:scroll;

それを、上記のように変更。
overflow-xは横軸、overflow-yは縦軸のスクロールを意味します。今回は縦軸のみでOK!
Image from Gyazo
無事にスクロールバーが表示されました♪

YouTubeを埋め込む②~チャンネルから動画データを取得~

せっかくなので、兄のYouTubeチャンネルから
最新の動画を引っ張ってきて表示されるようにしたいなと調べていたら
YouTube DATA APIでチャンネルごとや動画ごとにjsonデータを抽出できそうなのが判明!

最近習った、enebular(Node-RED)を使って、独自のWebAPIを作成しました!

▼作り方は以下記事にまとめてます。
YouTube DATA APIを使って、特定のチャンネルだけのjsonデータを取得する方法!!!

YouTubeを埋め込む③~フレームに動画データをはめこむ~

さぁ、あとは取得したデータをはめこむだけです!

簡単に行くかと思いきや、URLに変数データを代入するのに苦戦し、泣く泣く質問記事を投稿。
【質問記事】URLの中に変数を代入する方法を教えてください【Vue.js】

秒速で @kkent030315 さんから回答をいただき、あっという間に解決しました。
ありがとうございます><

        <div style="text-align: center;">    
            <iframe 
             width="240" height="180" 
             v-bind:src="video"
             frameborder="0" allow="accelerometer; autoplay; 
             encrypted-media; gyroscope; picture-in-picture" 
             allowfullscreen></iframe>
        </div>
      </div>
    </div>
    <script> 
        const app = new Vue({
          el: '#app',
          data: {
              video:''
          },
          mounted:function() {
            axios
            .get('WebAPIのURL')

            .then(response => {
              console.log(response.data)
              this.video="https://www.youtube.com/embed/"+response.data
            })
          }
        })
    </script>

これで、無事に動画を埋め込むことができました!!!

プレビュー

スマホで開かれることを想定しているので、Twitterにあげたスマホでのプレビュー動画をご覧ください!

良い感じ~~~~~~!

作った後に気づいたこと

WebAPIの制限

私がenebularで作ったWebAPI、時間制限あるんですね。笑
寝て起きたら、動画表示されなくなってました、、、
(スクールの先生が忠告してくれてた気はするのですが、その時よく理解していなかったすみません・・・)

(追記)同じスクールの同期も、同じ穴に落ちていたのがわかりました!笑
彼が解決策などもふまえてわかりやすく記事にしてくれているので、皆さんはこちらを参考にしていただければ…!
enebular上のNode-REDで作ったLINE BOTが1時間経ったら動かなくなる件の解決方法

まぁでも、新しい技術に触れられたからプラマイゼロということで!
兄のページは直しておきます…(笑)

サンプルを活用すれば、素敵なページを作れる!

改めて、この世には使えるものがたくさんあるなぁと実感しました。

ゼロから新しいものを作るのは素敵なことですが、かなり負荷がかかっちゃいます。
それがストレスになって一歩を踏み出せないぐらいなら、
楽しみながら作れる範囲で、使えるものを駆使して、作りたいものを程よいバランスで作っていく!

そういうアウトプットが大事だなぁと思いました。

最後に一言

デビッドちんすこうの応援よろしくお願いします!!!!!!!!!!

https://debichin.ml/

(*^^)v「よろしければLGTMもよろしくお願いします!」
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

『こんにちはPython』のスカッシュゲーム(壁打ちテニス)を JavaScript に写経してみた

『こんにちはPython』のスカッシュゲーム(壁打ちテニス)を JavaScript に写経してみた(前編)

みなさま、こんにちは。ハーツテクノロジーの山崎です。(この記事は、業務とは直接無関係な記事ですが、業務で得られた知見が間接的に随所に織り込まれていると思われます。)

この記事のきっかけは「こんにちはPython」という本です。

よい本です。なんと言っても、まんがであることが最高です。しかも、「ゲームセンターあらし」ですよ!(炎のコマは出ないけど)

あと、小学生にもわかるよう、丁寧に解説されている点もすばらしいです。めんどくさいから、「そういうもの」で片付けたいのがひとの心理だと思うのですが、そこはすがやみつる先生のやさしさなのだ、と思っています。

Python -> JavaScript に写経

で、この本、まんが版『こんにちはPython』では Python で説明されていますが、JavaScript で動かすこと(書き直してみること)にチャレンジしてみました。JavasScript で書くおすすめポイントはなんと言っても「ブラウザがあれば動いちゃうところ」です。

Python のコードはこちら↓にあります。

ゲームセンターあらしと学ぶプログラミング入門
まんが版『こんにちはPython』
https://www.m-sugaya.jp/manga_python/

(って、この↑ページからPythonのコードがダウンロードできるし、、、知らずに、マジメに本を見ながらコードを打ち込みました:sweat_smile:

ということで、まんが版『こんにちはPython』のスカッシュゲームを JavaScript に写経(書き直し)してみました。その結果がこちら↓。

写経のよいところは、それぞれのプログラミング言語の違い(よいところとか)や実行環境、ライブラリの違いがわかって、いろいろと発見があることです。

せっかくですので、わたしも小学生向けに、JavaScript の解説をしてみようと思います。ただ、志半ばで挫折し、前編と後編に分けました。前編までは小学生でもなんとか理解できるように努力はしました。が、前編で力尽き、後編では、おとな向けの解説になっています。ごめんなさい。

JavaScript コードの解説

はじめに、JavaScript は、いろいろなブラウザで動かすことができるのですが、ここではパソコン版の Google Chrome で動作を確認したコードを解説します。他のブラウザでは、動かないかもしれません(動くかもしれません)。すみませんが、Google Chrome は Windows, Mac ともに無料でダウンロードできますので、Google Chrome で確認してください。

さてここからは、すがやみつる先生にならって、なるべく小学生でも理解できるようにコードの解説をしてみたいと思います。

9のステップにわけて解説していきます。それぞれのステップで、プログラムが動くかどうか?理解できたかどうか?確認しながらすすめられるように書きました。

  • 1, 事前準備、HTML を少し書く
  • 2, 表示エリア div, canvas を用意
  • 3, はじめの一歩(表示してみる)
  • 4, canvasにボールを描いてみる
  • 5, アニメーションさせる!
  • 6, 壁の登場!
  • 7, 「ゲームオーバー」のコードを書く
  • 8, ラケットを動かす
  • 9, 最後に改造!

次のステップに行くは、前のステップにコードを追加する形で書いています。ちいさい単位で順番に理解していけば(おそらく)小学生でもわかるはず、、、です。

まんが版『こんにちはPython』が手元に無くても、わかるようには書いたつもりですが、やはり、本が手元にあったほうがわかりやすいと思います。

特にコードの解説に興味がない方は(おとなの方とかは)サクッと読み飛ばしてください。(おとな向けの記事は後編に書きます。)

それでは、はじめましょう。

1, 事前準備、HTML を少し書く

JavaScript でGUIを描くには、ブラウザの機能を使うのがラクチンです。ってか、もともとブラウザ内でプログラムを動かすためのスクリプトとして誕生したのが JavaScript なので、ブラウザ以外の選択肢を探すのは野暮ってことで?。

もとの Python のコードでは、GUI の表示にライブラリ tkinter を使っていましたが、JavaScript はブラウザで動かすのでブラウザに表示させる HTML ファイルを作り、HTML ファイルの中に JavaScript のコードを書きます。HTML の書き方について語り始めるときりがないので、ここではプログラミングの話に集中する目的として最小限のタグにしぼり、body タグと script タグの2つのタグだけ使うことにしました。

HTML の全体は、こんな感じ。

WebSquashGame.html
<body></body>
<script>
// ここにJavaScript のコードが入ります。
</script>

HTML の部分はこれだけ?

WebSquashGame.html とかファイルを作って、コードを書いて、ブラウザで開いてみると、真っ白な画面が表示されるはずです。

ちなみに、コードを書くツール(「エディタ」といいます)は、Visual Studio Code がおすすめです。無料だし、書いたコードに色がつくので、間違いに気が付きやすいです。あー、でも、デフォルト設定が英語ですね。日本語表示に変えるには、拡張パックが必要、、、うーん。小学生には難しいかも。おとなの人にたのんで入れてもらってください?<日本語拡張パック

image.png

(あー、あとで気がついたのだけど、メールアドレスを持っているなら、CodePen にアカウントを作るのもありかもしれない、、、。おとななら問題なくできると思うけど、さすがに小学生には無理かな。)

2, 表示エリア div, canvas を用意

Python では、GUI の表示に外部のライブラリ tkinter を使っていましたが、JavaScript ではブラウザのAPI、document.createElement(), document.body.appendChild() を使って表示エリアを用意します。

このコードで用意しているタグ(表示用のエリア)は、 メッセージ表示用に div タグと、お絵かきするキャンバス canvas タグの2つです。
cv.setAttribute() はキャンバスの属性を設定しています。ここではキャンバスの縦横の大きさを指定しています。

ということで、先程の HTMLファイルの <script> ... </script>の ... の部分に、JavaScript のコードをがんばって書いていきます。ここに書いたコードが、ブラウザを開いたときに実行され、うまく動けば実行結果が表示される仕組みです。

(各APIのおとな向けの説明はこのあたり createElementappendChildsetAttribute にあります。)

divタグとcanvasタグを用意するコード
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )

// キャンバス表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )

余談ですが、以下↓のように、HTML側にタグを書いて、document.querySelector() で参照しても結果は↑のコードと同じになります。HTML の一般的な書き方では、コンテンツの構造を HTMLタグで書いて、イベントなどの動的なアクションを JavaScript で書くといった棲み分けをします。ただ、今回は HTML タグの記述は減らして、JavaScript で構造からすべて書いてしまう方針で行きます。

?は余談なのでスルーしてね
<body>
  <div id="01">
  <canvas id="02" width="640px" height="480px" >
</body>
<script>
const div = document.querySelector('div#01')
const cv = document.querySelector('canvas#02')

// (以下省略)

</script>

(おとな向けの説明はこのあたり querySelectorCanvas_API にあります。)

3, はじめの一歩(表示してみる)

では、ブラウザに表示するところまでコードを全部書いてみます。

メッセージは innerText に表示したい文字列を代入する形で('=' の右に)書くと表示されます。

div.innerText = "はじめの一歩"

キャンバスは、コンテキストを取得して、コンテキストのAPI(ctx.fillStyle = 'silver'ctx.fillRect())を呼ぶと描画されます。

キャンバスをクリアするコード
const ctx = cv.getContext( '2d' ) // canvasからコンテキストを取得

// 画面クリア
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )

Python のコードでは四角を 'white' 白で塗っていましたが、ブラウザの背景もデフォルトで白いため、白い背景に白い四角を描いても見えないので、'silver' に変えました。

(おとな向けの説明はこのあたり innerTextfillStylefillRect にあります。)

さて、以下の 23 行のコードを HTML ファイル(WebSquashGame.html)に書いて、保存して、ブラウザで開いてみてください。プログラミングのはじまりです。

はじめの一歩(全コード)
<body></body>
<script>
// はじめの一歩

// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "はじめの一歩"

// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )

// 画面クリア
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )

</script>

このような結果が表示されるはずです。(↓ CodePen での実行結果)

表示されましたか?

表示できた方、おめでとうございます??。無事にプログラミングの一歩を踏み出せました。
表示されなかった方?は、おそらく、コードのどこかに間違いがあると思うので、「F12」キーを押して、Console にエラーメッセージが無いか確かめてください。エラーメッセージの示すところ(前後)をよーく見てみてください。なにかが違っているはず。がんばって見つけてね!

4, canvasにボールを描いてみる

プログラミングをうまくすすめるためには、なるべく小さい単位で動作を確認しながら、少しずつ進むことが得策です。
時間がもったいないと、山の頂上まで一気に駆け上がろうとして、足を滑らして転落したら、場所もさっぱりわからない状況、すなわち遭難してしまいます。(わたしは、そんな経験をもう何十回もしています?。)
ところどころに休憩ポイントを設けて、どのあたりにいるのかを確認しながら進むほうが、手戻りの時間が節約できて、結果的に早く目的地に着けるということです。急がば回れ。ウサギとカメのお話ですね。

前のステップが確認できたら次のステップに進みます。
次のステップは、ボールを描きます。

まず、ボールの大きさや表示する位置は、あとから変えられるように、変数や定数として名前をつけて確保します。
あとから変えるのが、変数。変えない(名前だけ付ける)のが、定数。です。

JavaScript では、変数には let、定数には const を付けて書きます。少し前までは変数に var を付けていましたが、var だと、スコープ(見える範囲)が広すぎて、間違って書き換えちゃったりするから、なるべくスコープの狭い let を使いましょう、、、ってことになっています。

// ボールを描く
let ball_ichi_x = 100
let ball_ichi_y = 100
const ball_size = 30

(おとな向けの説明はこのあたり letconst にあります。)

次に、ボールを描きます。ボールを描く位置は、用意した変数 ball_ichi_x, ball_ichi_y を使います。

具体的には、次のようなコードでボール(円)を描画します。ctx.arc() はボールというより「円弧を描くAPI」のことなのですが、ぐるっと一周すれば「円」になります。その「ぐるっと一周」が 2 * Math.PI というコードです。(このあたりも、小学生には難しすぎですね。残念ながらわかりやすく説明することができません。ごめんなさい。きっと、中学生か高校生にあたりで習うと思う。たぶん。)

ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()

(おとな向けの説明はこのあたり beginPatharcfill にあります。)

(おとな向けの追記があります。すがやみつる先生は小学生向けに、変数名にローマ字 ball_ichi_x, ball_ichi_y などを使って解説していますが、おとなのプログラマの方は英語で統一しましょう。ball_position_x とか、ball_pos_x とか。まぁ、x, y って書いたら、たいていは位置のことなので、 ball_x, ball_y が短いしベストかもしれません。)

まとめると次のようなコードになります。

canvasにボールを描くテスト(その1)(全コード)
<body></body>
<script>
// canvasにボールを描くテスト(その1)

// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "canvasにボールを描くテスト"

// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )

// 画面クリア
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )

// ボールを描く
let ball_ichi_x = 100
let ball_ichi_y = 100
const ball_size = 30
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()

</script>

ここで、関数の考え方を導入します。

関数は、プログラミングの世界ではとてもよく使う定番な機能(プログラミング手法)で、長く煩雑になりがちなコードをまとめて名前をつけておくことで、後から名前だけで呼び出すすことができるようになる、という便利な機能です。

次の例では、画面クリアのコードにdraw_screen()という名前を付けて、ボールを描くコードにdraw_ball()という名前を付けて関数にしています。あとで、これらの関数を呼び出すように変更します。

まず、「画面クリア」の2行のコードを draw_screen() という名前の関数に変更します。

// 画面クリア
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )

// 画面クリア
function draw_screen() {
    ctx.fillStyle = 'silver' // 'white'
    ctx.fillRect( 0, 0, cv_w, cv_h )
}

そして、「ボールを描く」の4行のコードを draw_ball() という名前の関数に変更します。

// ボールを描く
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()

// ボールを描く
function draw_ball() {
    ctx.beginPath()
    ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
    ctx.fillStyle = 'red'
    ctx.fill()
}

(おとな向けの説明はこのあたり Functions にあります。)

でもって、まとめて関数を呼び出します。呼び出すことで関数の中が実行されます。

// テストなので1回だけ実行
draw_screen() // 画面クリア
draw_ball()   // ボールを描く

さて、たくさん書いちゃいましたが、すべてをまとめると次の 42 行になります。

関数にする前と、関数にしたあとは、コードは違うのですがブラウザで開くとどちらも同じ結果になるので、見た目はまったく同じで区別がつかないはずです。

canvasにボールを描くテスト(関数版)(全コード)
<body></body>
<script>
// canvasにボールを描くテスト(関数版)

// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "canvasにボールを描くテスト"

// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )

// 画面クリア
function draw_screen() {
    ctx.fillStyle = 'silver' // 'white'
    ctx.fillRect( 0, 0, cv_w, cv_h )
}

// ボールを描く
let ball_ichi_x = 100
let ball_ichi_y = 100
const ball_size = 30
function draw_ball() {
    ctx.beginPath()
    ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
    ctx.fillStyle = 'red'
    ctx.fill()
}

// テストなので1回だけ実行
draw_screen() // 画面クリア
draw_ball()   // ボールを描く

</script>

きちんと書けると、こうなるはず↓。確認重要。

赤いボールが表示されたかな?

長くなってきたので、ブラウザで開く前に、よーく見てね。

5, アニメーションさせる!

このあたりから難しくなるのけど、いちばん楽しいところなので、踏ん張りどころです!張り切って行きましょう!

まず、ボールを移動するための変数を確保します。

// ボールを移動
let ball_idou_x = 15
let ball_idou_y = -15

次に、ボールを移動するコードを書いて関数 move_ball() にまとめます。
move_ball()が呼ばれたらボールの位置をボールの移動量だけ足して動かしています。

function move_ball() {
    // あとで、ここに「壁」と「ゲームオーバー」のコードが入ります

    // ボールを移動
    ball_ichi_x += ball_idou_x
    ball_ichi_y += ball_idou_y
}

このmove_ball()関数には、あとで「壁」のコードや「ゲームオーバー」のコードが追加されますので、覚えておいてください。

ちなみに、すがやみつる先生の Python のコードは、次のように canvas のエリア内のみ移動するように if 文で囲って書かれています。やさしいですね。しかし、わたしは、このあと「壁」のコードを書くので if 文は不要と勝手に判断し、シンプルに書いちゃいます?。

?ここは説明用コードです。スルーしてください。
function move_ball() {
    // ボールを移動
    if ( ball_ichi_x+ball_idou_x >= 0   ) {
        if ( ball_ichi_x+ball_idou_x <= cv_w ) {
            ball_ichi_x += ball_idou_x
        }
    }
    if ( ball_ichi_y+ball_idou_y >= 0   ) {
        if ( ball_ichi_y+ball_idou_y <= cv_h ) {
            ball_ichi_y += ball_idou_y
        }
    }
}

そして、アニメーションの最大のキモとなるAPI setInterval() の登場です。
こちらもブラウザに用意されている関数で、指定した時間ごとに関数を繰り返し呼び出してくれるという便利な機能です。数字 50 が呼ばれる時間間隔なので、ここでは 50ms ごとに game_loop() 関数が繰り返し呼ばれます。

繰り返し呼ばれることで、動いているように表示されます。すなわち「アニメーション」します。

// ゲームの繰り返し処理
function game_loop() {
    draw_screen() // 画面クリア
    draw_ball()   // ボールを描く
    move_ball()   // ボールを移動
    // あとで、ここに「ラケットを描く」関数呼び出しが入ります
}

// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps

(おとな向けの説明はこのあたり setInterval にあります。)

まとめると、こんな感じになります。

アニメーションさせるテスト(その1)(全コード)
<body></body>
<script>
// アニメーションさせるテスト(その1)

// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "アニメーションさせるテスト(その1)"

// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )

// 画面クリア
function draw_screen() {
    ctx.fillStyle = 'silver' // 'white'
    ctx.fillRect( 0, 0, cv_w, cv_h )
}

// ボールを描く
let ball_ichi_x = 100
let ball_ichi_y = 100
const ball_size = 30
function draw_ball() {
    ctx.beginPath()
    ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
    ctx.fillStyle = 'red'
    ctx.fill()
}

// ボールを移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
    // あとで、ここに「壁」と「ゲームオーバー」のコードが入ります

    // ボールを移動
    ball_ichi_x += ball_idou_x
    ball_ichi_y += ball_idou_y
}

// ゲームの繰り返し処理
function game_loop() {
    draw_screen() // 画面クリア
    draw_ball()   // ボールを描く
    move_ball()   // ボールを移動
    // あとで、ここに「ラケットを描く」関数呼び出しが入ります
}

// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps

</script>

ここで、ほんとに動くかどうか、アニメーションするかどうか確認してみましょう。このように?表示されるはずです。

見えましたか?赤いボールが、チラッと、右上の方向に動いていきます。そして、見えなくなります。ここでは「壁」がないので、それで正しい動きになります。
見逃した人は↑の右下のすみにある「Rerun」(再実行)のボタンを押してみましょう。最初から実行されるので見えると思います。

6, 壁の登場!

ここで「壁(中ボス)」の登場です。

ボールを動かすと、壁を通り抜けて、どこまでも行ってしまい帰ってきません。「壁」のコードを書いていないからです。

「壁」は上下左右に4箇所あります。それぞれの壁にぶつかった場合、ボールを反転させるコードを書きます。
反転させるには、移動量にマイナス1を掛けることで実現しています。

この「壁」のコードは、関数 move_ball() の中に追加します。(書く位置がわかりにくいかもしれないので、全コードを見て確認してください)

ボール移動の関数move_ball()の中に追加します。
    // 左の壁に当たったのかの判定
    if ( ball_ichi_x+ball_idou_x < 0 ) {
        ball_idou_x *= -1
    }

    // 左の壁に当たったのかの判定
    if ( ball_ichi_x+ball_idou_x > cv_w ) {
        ball_idou_x *= -1
    }

    // 天井に当たったのかの判定
    if ( ball_ichi_y+ball_idou_y < 0 ) {
        ball_idou_y *= -1
    }

    // 床に当たったのかの判定(あとで「ゲームオーバー」のコードに変わります)
    if ( ball_ichi_y+ball_idou_y > cv_h ) {
        ball_idou_y *= -1
    }

あと、ボールのサイズも小さくconst ball_size = 10にしています。

はたして、中ボス、クリアなるか!!!コードの全文はこちら!長い!!!!
とは言っても 80行。まだまだ少ない方です?。

アニメーションさせるテスト(その2)(全コード)
<body></body>
<script>
// アニメーションさせるテスト(その2)

// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "アニメーションさせるテスト(その2)"

// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )

// 画面クリア
function draw_screen() {
    ctx.fillStyle = 'silver' // 'white'
    ctx.fillRect( 0, 0, cv_w, cv_h )
}

// ボールを描く
let ball_ichi_x = 0
let ball_ichi_y = 250
const ball_size = 10
function draw_ball() {
    ctx.beginPath()
    ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
    ctx.fillStyle = 'red'
    ctx.fill()
}

// ボールの移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
    // 左の壁に当たったのかの判定
    if ( ball_ichi_x+ball_idou_x < 0   ) {
        ball_idou_x *= -1
    }

    // 左の壁に当たったのかの判定
    if ( ball_ichi_x+ball_idou_x > cv_w ) {
        ball_idou_x *= -1
    }

    // 天井に当たったのかの判定
    if ( ball_ichi_y+ball_idou_y < 0 ) {
        ball_idou_y *= -1
    }

    // ここに(床に当たる前に)あとで「ラケットに当たったのかの判定」のコードを入れます

    // 床に当たったのかの判定(あとで「ゲームオーバー」のコードに変わります)
    if ( ball_ichi_y+ball_idou_y > cv_h ) {
        ball_idou_y *= -1
    }

    // ボールを移動
    ball_ichi_x += ball_idou_x
    ball_ichi_y += ball_idou_y
}

// ゲームの繰り返し処理
function game_loop() {
    draw_screen() // 画面クリア
    draw_ball()   // ボールを描く
    move_ball()   // ボールの移動
    // あとで、ここに「ラケットを描く」関数呼び出しが入ります
}

// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps

</script>

ボールがずっと壁に跳ね返ってるアニメーションになればプログラミング成功です!!?

できたかな? ここまでくれば中ボスクリア?です。おめでとう!!

7, 「ゲームオーバー」のコードを書く

アニメーションの次はいよいよ「ゲーム」にします。

アニメーションは、ずーーと、同じことを繰り返し表示しています。スタートも無ければ、ゲームオーバーもありません。これでは「ゲーム」とは言えません。

「ゲーム」にするには、具体的には、is_gameover という変数を用意して、true のときは「ゲームオーバー」とし、逆に false のときは、ゲーム中(ボールを動かすアニメーションをする)とします。このis_gameover という変数に状態を入れて、ゲーム中なのか、ゲームオーバー(スタート待ち)なのかを切り替えるやりかたは、わりとよく使われるプログラミングのテクニックです。

また、ここで、点数を計算するための変数 point も用意しちゃいましょう(あとで使います)。

let is_gameover = true // true のときは「ゲームオーバー」、false のときは「ゲーム中」
let point = 0

そして、ボールの位置をゲームスタートの状態に戻す「初期化」の関数を書き、マウスをクリックしたらこの関数が呼ばれるように書きます。

// ゲームの初期化
let is_gameover = true // true のときは「ゲームオーバー」、false のときは「ゲーム中」
let point = 0
function init_game() {
    is_gameover = false
    point = 0

    ball_ichi_x = 0
    ball_ichi_y = 250
    ball_idou_x = 15
    ball_idou_y = -15

    div.innerText = "ゲームスタート!"
}

// クリックで再スタート
cv.onclick = ev => {
    if ( is_gameover )
        init_game() // ゲームの初期化
}

次に、スカッシュゲームにするために、壁の一つ(床の部分)をゲームオーバーのコードに書き換えます。

床にあたったのかの判定をゲームオーバーの判定に変える
    // 床に当たったのかの判定(あとで「ゲームオーバー」のコードに変わります)
    if ( ball_ichi_y+ball_idou_y > cv_h ) {
        ball_idou_y *= -1
    }

    // ゲームオーバーの判定
    if ( ball_ichi_y+ball_idou_y > cv_h ) {
        is_gameover = true
        div.innerText = "ゲームオーバー!"
        return // ゲームオーバーなのでボールの移動はせずに関数から抜けて帰る
    }

そして、大切なのが「ゲームオーバーのときはボールの移動はしない」というコード↓

    if ( is_gameover ) return // ゲームオーバーなので関数から抜けて帰る

この「ゲームオーバーのときはボールの移動はしない」コードをボールを移動する関数function move_ball() {に入れます。

ここまでをまとめると以下のコードになります。ここまでくると、いよいよゲームの土台になってきた!!という感じが伝わってくると思います。

ゲームオーバーのテスト(全コード)
<body></body>
<script>
// ゲームオーバーのテスト

// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "ゲームオーバーのテスト:クリックでスタート"

// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )

// 画面クリア
function draw_screen() {
    ctx.fillStyle = 'silver' // 'white'
    ctx.fillRect( 0, 0, cv_w, cv_h )
}

// ボールを描く
let ball_ichi_x = 0
let ball_ichi_y = 250
const ball_size = 10
function draw_ball() {
    ctx.beginPath()
    ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
    ctx.fillStyle = 'red'
    ctx.fill()
}

// ボールの移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
    if ( is_gameover ) return // ゲームオーバーなので関数から抜けて帰る

    // 左右の壁に当たったのかの判定
    if ( ball_ichi_x+ball_idou_x < 0 ) {
        ball_idou_x *= -1
    }
    if ( ball_ichi_x+ball_idou_x > cv_w ) {
        ball_idou_x *= -1
    }

    // 天井の壁に当たったのかの判定
    if ( ball_ichi_y+ball_idou_y < 0 ) {
        ball_idou_y *= -1
    }

    // ここに(床に当たる前に)あとで、「ラケットに当たったのかの判定」のコードを入れます

    // 床にあたったのかの判定を、ゲームオーバーの判定に変えた
    if ( ball_ichi_y+ball_idou_y > cv_h ) {
        is_gameover = true
        div.innerText = "ゲームオーバー!"
        return // ゲームオーバーなのでボールの移動はせずに関数から抜けて帰る
    }

    // ボールを移動
    ball_ichi_x += ball_idou_x
    ball_ichi_y += ball_idou_y
}

// ゲームの初期化
let is_gameover = true
let point = 0
function init_game() {
    is_gameover = false
    point = 0
    ball_ichi_x = 0
    ball_ichi_y = 250
    ball_idou_x = 15
    ball_idou_y = -15

    div.innerText = "ゲームスタート!"
}

// クリックで再スタート
cv.onclick = ev => {
    if ( is_gameover )
        init_game() // ゲームの初期化
}

// ゲームの繰り返し処理
function game_loop() {
    draw_screen() // 画面クリア
    draw_ball()   // ボールを描く
    move_ball()   // ボールの移動
    // あとで、ここに「ラケットを描く」関数呼び出しが入ります
}

// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps

</script>

ちゃんと書けると、こうなるはず!↓

ゲームっぽくなってきました。
ここまでくれば、あと少しです!!がんばれー!!!

8, ラケットを動かす

さて、最後に「ラケット」(=ラスボス)の登場です。ラケットにあたると point が加算されます。この中ボスがクリアができると、待望のゲームになります。

まず、ラケットの描画に必要となる変数と定数(racket_ichi_x, racket_size)を追加します。

そして、draw_racket()という関数と、マウスの位置とラケットの位置が同じになるコードracket_ichi_x = ev.offsetXも追加します。

// ラケットを描く
let racket_ichi_x = 0
const racket_size = 100
function draw_racket() {
    ctx.fillStyle = 'yellow'
    ctx.fillRect( racket_ichi_x, cv_h-10, racket_size, 8 )
}
// マウスの動きの処理
cv.onmousemove = ev => {
    racket_ichi_x = ev.offsetX
}

でもって、draw_racket()を geme_loop() 関数の中に追加して、

// ゲームの繰り返し処理
function game_loop() {
    draw_screen() // 画面クリア
    draw_ball()   // ボールを描く
    move_ball()   // ボールの移動
    draw_racket() // ラケットを描く ←これ
}

そして、move_ball()関数の中に「ラケット」あたった判定コードを書き加えます。なんと、if 文が三つも!!!!ひー!!さすがに難しいか!!よーく見て撃破してください。

ここ?がラスボス!!!

    // ラケットに当たったのかの判定
    if ( ball_ichi_y+ball_idou_y > cv_h-10 ) {
        if ( racket_ichi_x < ball_ichi_x+ball_idou_x ) {
            if ( ball_ichi_x+ball_idou_x < racket_ichi_x+racket_size ) {
                ball_idou_y *= -1
                point += 10
                div.innerText = '得点:' + point
            }
        }
    }

このラスボスをクリアすると、ようやく「ゲーム」になります。ここまで長い道のりでした!!!

さて!全部まとめると!これ?

コードをよーく見比べて、実行してください。

<body></body>
<script>
// 「こんにちはPython」のスカッシュゲーム(壁打ちテニス)をJavaScriptで写経(最小版)

// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "スカッシュゲーム:マウスクリックでスタート!"

// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )

// 画面クリア
function draw_screen() {
    ctx.fillStyle = 'silver' // 'white'
    ctx.fillRect( 0, 0, cv_w, cv_h )
}

// ボールを描く
let ball_ichi_x = 0
let ball_ichi_y = 250
const ball_size = 10
function draw_ball() {
    ctx.beginPath()
    ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
    ctx.fillStyle = 'red'
    ctx.fill()
}

// ボールの移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
    if ( is_gameover ) return

    // 左右の壁に当たったのかの判定
    if ( ball_ichi_x+ball_idou_x < 0   ) {
        ball_idou_x *= -1
    }
    if ( ball_ichi_x+ball_idou_x > cv_w ) {
        ball_idou_x *= -1
    }

    // 天井の壁に当たったのかの判定
    if ( ball_ichi_y+ball_idou_y < 0 ) {
        ball_idou_y *= -1
    }

    // ラケットに当たったのかの判定
    if ( ball_ichi_y+ball_idou_y > cv_h-10 ) {
        if ( racket_ichi_x < ball_ichi_x+ball_idou_x ) {
            if ( ball_ichi_x+ball_idou_x < racket_ichi_x+racket_size ) {
                ball_idou_y *= -1
                point += 10
                div.innerText = '得点:' + point
            }
        }
    }

    // ミスした時の判定
    if ( ball_ichi_y+ball_idou_y > cv_h ) {
        div.innerText = 'ゲームオーバー!得点:' + point
        is_gameover = true
        return
    }

    // ボールを移動
    ball_ichi_x += ball_idou_x
    ball_ichi_y += ball_idou_y
}

// ラケットを描く
let racket_ichi_x = 0
const racket_size = 100
function draw_racket() {
    ctx.fillStyle = 'yellow'
    ctx.fillRect( racket_ichi_x, cv_h-10, racket_size, 8 )
}

// マウスの動きの処理
cv.onmousemove = ev => {
    racket_ichi_x = ev.offsetX
}

// ゲームの初期化
let is_gameover = true
let point = 0
function init_game() {
    is_gameover = false
    point = 0
    ball_ichi_x = 0
    ball_ichi_y = 250
    ball_idou_x = 15
    ball_idou_y = -15

    div.innerText = "スカッシュゲーム:スタート!"
}

// クリックで再スタート
cv.onclick = ev => {
    if ( is_gameover )
        init_game() // ゲームの初期化
}

// ゲームの繰り返し処理
function game_loop() {
    draw_screen() // 画面クリア
    draw_ball()   // ボールを描く
    move_ball()   // ボールの移動
    draw_racket() // ラケットを描く
}

// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps

</script>

コードが入力できれば、こうなる↓はず!!!

ここまでで最小版の完成です。お疲れ様でした!!!!???

しばらく遊んでみて、問題が無いか確認してください。

9, 最後に改造!

ここまでで、JavaScript で「ゲーム」を作るのに必要な最小の要素は説明しました。

ここからは、みなさんの豊かな発想で、好きなように改造してください。
プログラミングとは、発想しだい、組み合わせしだいで、だれも見たこともないゲームになったり、多くの人にたのしさを与える、とてもワクワクすることなのです。

どんな発想をするか、どんな改造をするか、それともゼロから新しいモノを作るのか、みなさんのエネルギーに期待しています。

ちなみに、「こんにちは Python」では、こんな感じ↓に仕上げています。どこに、どんな改造をしているのかは、コードをよーく見比べてみてください。もしくは、「こんにちは Python」の本を買って読んでみるのもよいかもしれません。

<body></body>
<script>
// 「こんにちは Python」のスカッシュゲーム(壁打ちテニス)を JavaScript で写経(音無し版)

// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "スカッシュゲーム(音無し版):マウスクリックでスタート!"

// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )

// 画面クリア
function draw_screen() {
    ctx.fillStyle = 'silver' // 'white'
    ctx.fillRect( 0, 0, cv_w, cv_h )
}

// ボールを描く
let ball_ichi_x = 0
let ball_ichi_y = 250
const ball_size = 10
function draw_ball() {
    ctx.beginPath()
    ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
    ctx.fillStyle = 'red'
    ctx.fill()
}

// ボールの移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
    if ( is_gameover ) return

    // 左右の壁に当たったのかの判定
    if ( ball_ichi_x+ball_idou_x < 0   ) {
        ball_idou_x *= -1
    }
    if ( ball_ichi_x+ball_idou_x > cv_w ) {
        ball_idou_x *= -1
    }

    // 天井の壁に当たったのかの判定
    if ( ball_ichi_y+ball_idou_y < 0 ) {
        ball_idou_y *= -1
    }

    // ラケットに当たったのかの判定
    if ( ball_ichi_y+ball_idou_y > cv_h-10 ) {
        if ( racket_ichi_x < ball_ichi_x+ball_idou_x ) {
            if ( ball_ichi_x+ball_idou_x < racket_ichi_x+racket_size ) {
                ball_idou_y *= -1
                if ( Math.random() < 0.5 )
                    ball_idou_x *= -1

                const mes = Math.floor( Math.random() * 5 )
                let message = ''
                if ( mes == 0 )
                    message = 'うまい!'
                if ( mes == 1 )
                    message = 'グッド!'
                if ( mes == 2 )
                    message = 'ナイス!'
                if ( mes == 3 )
                    message = 'よしッ!'
                if ( mes == 4 )
                    message = 'すてき!'

                point += 10

                div.innerText = message + ' 得点:' + point
            }
        }
    }

    // ミスした時の判定
    if ( ball_ichi_y+ball_idou_y > cv_h ) {
        const mes = Math.floor( Math.random() * 3 )
        let message = ''
        if ( mes == 0 )
            message = 'へたくそ!'
        if ( mes == 1 )
            message = 'ミスしたね!'
        if ( mes == 2 )
            message = 'あーあ、見てられないね!'

        div.innerText = message + ' 得点:' + point

        is_gameover = true
        return
    }

    // ボールを移動
    ball_ichi_x += ball_idou_x
    ball_ichi_y += ball_idou_y
}

// ラケットを描く
let racket_ichi_x = 0
const racket_size = 100
function draw_racket() {
    ctx.fillStyle = 'yellow'
    ctx.fillRect( racket_ichi_x, cv_h-10, racket_size, 8 )
}

// マウスの動きの処理
cv.onmousemove = ev => {
    racket_ichi_x = ev.offsetX
}

// ゲームの初期化
let is_gameover = true
let point = 0
function init_game() {
    is_gameover = false
    ball_ichi_x = 0
    ball_ichi_y = 250
    ball_idou_x = 15
    ball_idou_y = -15
    point = 0

    div.innerText = "スカッシュゲーム:スタート!"
}

// クリックで再スタート
cv.onclick = ev => {
    if ( is_gameover )
        init_game() // ゲームの初期化
}

// ゲームの繰り返し処理
function game_loop() {
    draw_screen() // 画面クリア
    draw_ball()   // ボールを描く
    draw_racket() // ラケットを描く
    move_ball()   // ボールの移動
}

// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps

</script>

最初に載せたサンプルは音が出ますが、こちらは音無しです。音の有無以外は同じです。
ごめんなさい。音のプログラムの説明は後編で解説させてください。ここに載せるにはちょっと難しい内容なので。

それでは、以上で、JavaScript プログラミングの解説を終わります。

おそらく、わからないところや聞きたいところもたくさんあると思います。がんばって、自分のちからで突破してほしいと思います。ですが、どーしても解決できないときは、おとなのひとやここ(インターネット)に質問することもありです。

まんが版『こんにちはPython』を買うのもおすすめします。

「あきらめたらそこでゲームオーバー」なので、コツコツとゆっくりでもよいので、たのしんで続けてください。

みなさまの豊かなプログラミングライフを応援しつつ。

おまけ

ここからは、おとな向けの記事になります。

うーん。小学生にプログラミングを解説するのって、とても難しいです。不可能とも思えてきました。

まだまだ、書き残したことがあります。それは「音を出す」と「スマホ対応」です。さらには「リファクリング」というか、canvas より SVG に書き換えてみたいとか、描画速度を最適化してみたいとか、そんなところです。

これらの解説は、小学生向けに書き続けることに限界を感じたので、別の記事に分けたいと思います。

書き終わりましたら、こちらにリンクを貼りますので、もうしばらくお待ちください。

書き終わりました。おとなの方はこちらからどうぞ→後編。小学生の方は、読んでもいいけど、たぶん理解できないと思います。

どうぞよろしくお願いいたします?。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

axiosを使ってみる

【script指定】
CDN版Vue.js + axios でさくっとAjaxしてみる
JSDELIVERのaxiosページ

【使い方】axiosとは

【GET】

axios.get('/user?ID=12345')
.then(function (response) {
    // handle success
  console.log(response);
})
.catch(function (error) {
    // handle error
  console.log(error);
})
.finally(function () {
    // always executed
});

【POST】

const params = new URLSearchParams();
params.append('firstName', 'Mineo');
params.append('lastName', 'Okuda');
axios.post('post-api.php',params)
.then(function (response) {
    // handle success
  console.log(response);
})
.catch(function (error) {
    // handle error
  console.log(error);
})
.finally(function(){
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Twitter Web版からトレンドを消し去りGoogleカレンダーを召喚する(Chrome/Edge向け)

はじめに

近頃のTwitterでは、政治や性差別、人権問題などの激しい議論を招きやすい話題がトレンドにピックアップされることが多く、これを経由して過激な言動をするアカウントが目に入る場面が増えました。

そこで、トレンド表示をオフにするChrome拡張機能の導入が選択肢として挙がります。
しかし、これを使うとページの右側が空白になってしまいちょっと寂しいです。

そこで本記事では、以下の画像のようにトレンド・おすすめユーザーを非表示化し、空いたスペースにGoogleカレンダーを召喚する方法を紹介します。これで、Twitterを見ているだけで日頃の予定管理はバッチリです!

なお、本記事の方法は基本的にChromeおよび新Edge向けに書いています。
Firefoxでも可能であることは確認できましたが、セキュリティ上の観点であまりお勧めできません(後述するContent-Security-Policyの回避を特定サイト内の特定サイトへのiframeのみに対して安全に設定できるアドオンが現状見当たらないため)。

手順の概要

今回は、JavaScriptを実行してWebサイトの書き換えなどを行うGreasemonkeyを使用してTwitterページにGoogleカレンダーのHTMLタグを挿入します。

大まかに、以下の手順で行います。
1. Greasemonkeyを動かすためのプラグインを入れる
2. Greasemonkeyスクリプトを追加する
3. Googleカレンダーの設定から埋め込み用HTMLタグを取得して、Greasemonkeyスクリプト内に書き込む
4. Twitterサイト上でiframeタグを許可するためのプラグインを入れる

具体的なやり方

1. Greasemonkeyを動かすためのプラグインを入れる

Chrome・Edgeに以下の拡張機能を導入してください。

これでGreasemonkeyが使えるようになり、特定のWebサイト上でJavaScriptを自動で走らせたりできるようになります。

2. Greasemonkeyスクリプトを追加する

Tampermonkeyに新しいスクリプトを追加します。

①「新規スクリプトを追加」を押す

image.png

②以下のスクリプトを貼り付ける

// ==UserScript==
// @name         Twitterサイドバーへカレンダー召喚
// @namespace    https://qiita.com/m4saka
// @version      0.1
// @author       masaka
// @match        https://*.twitter.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Googleカレンダーの埋め込み用HTMLタグをここへ貼り付け
    const calendarHtml = '※※※※※※※※';

    setInterval(
        function() {
            // おすすめユーザーやトレンドを全てカレンダーに置き換える
            const elements = document.getElementsByClassName('css-1dbjc4n r-1ylenci r-1phboty r-rs99b7 r-ku1wi2 r-1udh08x');
            for (let i = 0; i < elements.length; i++) {
                if (i == 0) elements[i].outerHTML = calendarHtml;
                else elements[i].outerHTML = '';
            }

            // カレンダーが2個以上できた場合は1個にする
            const calendars = document.getElementsByClassName("calendar");
            for (let i = 1; i < calendars.length; i++) {
                calendars[i].outerHTML = '';
            }
        }, 10
    );
})();

このスクリプトのうち、const calendarHtml = '※※※※※※※※';となっている部分は、後の手順で取得する埋め込み用HTMLタグを入力します。

3. Googleカレンダーの設定から埋め込み用HTMLタグを取得して、Greasemonkeyスクリプト内に書き込む

①以下のURLからGoogleカレンダーへアクセス

②ページ右上の歯車ボタンから「設定」をクリック

 image.png

③左側メニューから表示したいカレンダーを選択し、「カレンダーの統合」をクリック

 image.png

④「埋め込みコード」をコピーし、Greasemonkeyスクリプト中に貼り付け

以下の部分からコピーできます。
 image.png
(必須ではないですが、下の「カスタマイズ」ボタンを押すと若干見た目をいじることもできます。記事の最初にあった画面例はカレンダー名や日/週/月メニューをカスタマイズで非表示にしています。)

スクリプト中の「※※※※※※※※」の部分にペーストします。
ペーストすると以下のようになります。

// Googleカレンダーの埋め込み用HTMLタグをここへ貼り付け
const calendarHtml = '<iframe src="https://calendar.google.com/calendar/embed?src=...........';

⑤iframeタグ中に「class="calendar"」を追記

以下のように「class="calendar"」を追記してください。
(これはGreasemonkeyスクリプト中から要素を参照するために使用しています)

const calendarHtml = '<iframe class="calendar" src="https://calendar.google.com/calendar/embed?src=...........';

⑥iframeタグ中の「width="800"」を削除

そのままだとカレンダーがサイドバーの横幅をはみ出してしまうので、以下のwidth="800"の部分を削除します。

const calendarHtml = '<iframe ... style="border: 0" width="800" height="600" frameborder=...';
                                                    ^^^^^^^^^^^

終わったら、Ctrl+Sキーを押してスクリプトを保存します。

4. Twitterサイト上でiframeタグを許可するためのプラグインを入れる

この状態でTwitterを開くと、以下のようにサイドバーに何かは表示されます。
image.png

コンテンツがブロックされた旨が表示されてしまっています。これは、Twitterがセキュリティ上の理由でページ内のiframeの使用を制限しているためです。
(HTTPレスポンスヘッダ内のContent-Security-Policyで特定のホストのみに制限されています。)

そこで、拡張機能を入れてTwitter上でのGoogleカレンダーの埋め込みのみを許可するルールを記述します。
(このほかにContent-Security-Policyヘッダを全サイトで無効化する拡張機能を入れる方法もありますが、これを入れるとセキュリティ上危険な状態となるのであまりお勧めできません。)

Chrome・Edgeに以下の拡張機能を導入してください。

拡張機能のオプションを開くと入力欄が出てくるので、書かれている内容を以下のものに置き換えます。

[
    ["https://.*\\.?twitter\\.com", [
        ["frame-src", "frame-src https://calendar.google.com"]
    ]]
]

以下のように貼り付け、×ボタンで閉じます。
image.png

これにより、twitter.com内でのcalender.google.comへのiframeのみ例外的に認めることができました。
Twitterに再度アクセスすると、以下のようにきちんと表示されるようになっています。

image.png

もし上手く反映されない場合は、Ctrl+F5を押してページを更新してみてください。
また、OSの再起動で治る場合もあります(Chromeでは初回に毎回Ctrl+F5を押さないとカレンダーが表示されない現象が生じましたが、その場合はOSの再起動で治りました。何らかのキャッシュの影響?)。

もしどうしても上手く行かない場合は、拡張機能「Ignore X-Frame headers」をインストールし、サイトデータの読み取りをtwitter.comのみにすることで、Twitter上のContent-Security-Policyをすべて無効化してカレンダーを表示できるようにできます(セキュリティ上あまりお勧めできません)。

色味を調整する

この状態でも使えるのですが、カレンダーの配色が鮮やかすぎて、タイムラインを見ているとつい視線がそっちに向いてしまいます。

Greasemonkeyスクリプトに記述したiframeタグに以下のように「style="filter:grayscale(0.75)"」という記述を追加することで、彩度を下げることができます。
(※古いブラウザの場合はfilter:~-webkit-filter:~に書き換える必要があるかも知れません。)

const calendarHtml = '<iframe style="filter:grayscale(0.75)" class="calendar" src=...........';

image.png

これで目障りではなくなりました。

Twitterのテーマをブラックやダークブルーにしている場合は、style="filter:grayscale(0.75) invert(1) hue-rotate(180deg)"にすると良い感じです。

image.png

お疲れ様でした。
それでは、良いTwitterライフを!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

備忘録 Javascript 配列のデータ抽出 filter

Javascript filter

基本(数字)

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var result = array.filter((value)=>{
    //valueには配列の要素が入る(1 2 3 ... 9 10)
    //valueが5よりも小さい数だけ抽出
    return value < 5;
})

console.log(result);
// [1, 2, 3, 4] という新しい配列ができる。

基本②(文字列)

var string_array = ["apple", "orange", "peach", "lemon"];

var result = string_array.filter((value) => {
    return value === "apple" || value == "orange";
})

console.log(result);
//["apple", "orange"] という新しい配列ができる。

重複を操作

var array = [2, 3, 4, 5, 3, 4, 5, 6, 7, 8, 6];

var result = array.filter((value, index, array) => {
    return array.indexOf(value) === index;
})
console.log(result);
//[2, 3, 4, 5, 6, 7, 8]

オブジェクト

※ array.filter(callback,object)とするとthisでobjectを参照できる。

var students_id = {
    1: "tanaka",
    2: "suzuki",
    3: "sato",
    4: "nakata",
    5: "nakamura" 
}

var ids = [1, 2, 3, 5, 7, 8, 9];

var result = ids.filter(function(value){
    for(var id in this){
        if(id == value){
            return value;
        }
    }
},students_id)

console.log(result);
//[1, 2, 3, 5]

参考

https://www.sejuku.net/blog/21887

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む