0
2

More than 1 year has passed since last update.

Vue.jsで作図する 凸レンズの焦点問題

Last updated at Posted at 2021-03-07

突然ですが、こんな図みたことありますよね?
スクリーンショット 2021-03-07 002551.png
これは理科で定番の凸レンズの焦点に関する問題です。
今回はこの焦点問題を理解しつつVue.jsで作図することを目指します。

github ソースコード:
https://github.com/tenugui-taro/convex-lens_public

作ったもの ※PC推奨:
※2021/04 削除しました

##準備
Vue2 × composition-api × Vuetify の構成で作ります。

$ vue create {project-name}
$ npm install @vue/composition-api
$ vue add vuetify

composition-apiを有効にするにはmain.tsで設定が必要になります。
@vue/composition-api

src/main.ts
import VueCompositionAPI from '@vue/composition-api'

Vue.use(VueCompositionAPI)

##焦点問題とは
ざっくりいうと凸レンズに入射した光が集まる位置に実像ができる。
虫眼鏡でものが大きくみえる現象はこの原理を利用していたりする。

焦点は凸レンズに平行に入射した光が集まる場所
ちなみに、凸レンズの中心から焦点までの距離の2倍に光源があると光源と実像は同じ大きさになる。

解答のポイントは光の線を引きその交点を求めること。

  • 光軸と平行な線
  • 凸レンズの中心を通る線
  • 焦点を通る線

スクリーンショット 2021-03-08 043257.png

##線を引く
解答のポイントとなる、光源から出る線を引く方法を考えます。
###vuetifyで作図
Vuetifyのドキュメントを眺めているとグラフ作成用UIコンポーネントがありました。
v-sparkline

なんとなくおしゃれだしこれで。
まずはサンプルをcomposition-apiを使いつつコンポーネント化します。

src/components/Sparkline.vue
<template>
  <v-sparkline
    :value="value"
    :gradient="gradient"
    :smooth="radius || false"
    :padding="padding"
    :line-width="width"
    :stroke-linecap="lineCap"
    :gradient-direction="gradientDirection"
    :fill="fill"
    :type="type"
    :auto-line-width="autoLineWidth"
    auto-draw
  />
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from "@vue/composition-api";

const GRADIENTS: string[][] = [
  ["#222"],
  ["#42b3f4"],
  ["red", "orange", "yellow"],
  ["purple", "violet"],
  ["#00c6ff", "#F0F", "#FF0"],
  ["#f72047", "#ffd200", "#1feaea"],
];

export default defineComponent({
  setup() {
    const state = reactive({
      width: 2, // 線の太さ
      radius: 10, // 線同士のつながり 小さいほど角張る
      padding: 8, // 描画エリアの余白
      lineCap: "round", // 線端の形状
      gradient: GRADIENTS[5], // 配色 
      value: [0, 2, 5, 9, 5, 10, 3, 5, 0, 0, 1, 8, 2, 9, 0], // 値
      gradientDirection: "top", // グラデーションの方向
      fill: false, // グラフの塗りつぶし
      type: "trend", // グラフの種類 trend or line
      autoLineWidth: false, // 自動で線を拡張してくれる?
    });

    return {
      ...toRefs(state),
    };
  },
});
</script>

おしゃれなグラフが表示されました。
sparkline4.gif

焦点問題の作図をイメージして、3本の線を引きます。
線を重ねるためにpositionを設定しています。

src/App.vue
<div style="position: relative">
  <Sparkline style="position: absolute" :value="[100, 100, 0]" />
  <Sparkline style="position: absolute" :value="[100, 0, -100]" />
  <Sparkline style="position: absolute" :value="[100, -100, -100]" />
</div>

sparkline5.gif

いい感じ......
あれ、他要素との座標をどうやって合わせる......?

ただ単純に用意した画像を重ねると以下の通り、これでは0点です。
スクリーンショット 2021-03-07 162240.png
ここからどうするか......
ごまかしでwidthやheightを調整するうちに心が痛くなったので別の手段を考えます:innocent:

###svgで表現する
図形を組み合わせながら問題を用意します。

src/App.vue
<svg
  xmlns="http://www.w3.org/2000/svg"
  width="90vw"
  height="70vh"
  id="drawing-area"
>
  <ellipse cx="50%" cy="50%" rx="3%" ry="40%" fill="white" stroke="black" />
  <line
    x1="10%"
    y1="50%"
    x2="90%"
    y2="50%"
    stroke-width="1%"
    stroke="#455A64"
  />
  <circle cx="35%" cy="50%" r="1%" fill="#E0E0E0" stroke="black" />
  <circle cx="65%" cy="50%" r="1%" fill="#E0E0E0" stroke="black" />
  <rect x="20%" y="30%" width="1%" height="20%" fill="#e74c3c" />
</svg>

スクリーンショット 2021-03-08 010407.png
いよいよ作図します。
線を描画する関数、実像を描画する関数を用意しひとまず手計算した値を渡します。

src/utils/addLine.ts
/**
 * 線を描画
 * @param {number[]} params
 */
export const addLine = (params: number[]) => {
    const drawingArea = document.getElementById("drawing-area");
    const line = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "line"
    );

    line.setAttribute("x1", `${params[0]}%`);
    line.setAttribute("y1", `${params[1]}%`);
    line.setAttribute("x2", `${params[2]}%`);
    line.setAttribute("y2", `${params[3]}%`);
    line.setAttribute("stroke", "black");
    drawingArea!.appendChild(line);
}
src/utils/addRect.ts
/**
 * 四角形を描画
 * @param {number[]} params
 */
export const addRect = (params: number[]) => {
    const drawingArea = document.getElementById("drawing-area");
    const rect = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "rect"
    );

    rect.setAttribute("x", `${params[0]}%`);
    rect.setAttribute("y", `${params[1]}%`);
    rect.setAttribute("width", `${params[2]}%`);
    rect.setAttribute("height", `${params[3]}%`);
    rect.setAttribute("fill", `#e74c3c`);
    drawingArea!.appendChild(rect);
}
src/App.vue
onMounted(() => {
  // addLine([x1, y1, x2, y2])
  // 凸レンズの中心を通る線
  addLine([20, 30, 80, 70]);

  // 光軸に対して平行な線
  addLine([20, 30, 50, 30]);
  addLine([50, 30, 80, 70]);

  // 焦点を通る線
  addLine([20, 30, 50, 70]);
  addLine([50, 70, 80, 70]);

  // addRect([x, y, width, height])
  // 実像
  addRect([80, 50, 1, 20]);
});

スクリーンショット 2021-03-08 010125.png
作図できました!100点:cherry_blossom::cherry_blossom:

##自動化
もちろん手計算は面倒です。
交点を算出する関数を用意し、vueファイルとは別に切り出します。
計算ばかりなので興味ある方はソースコードを参照ください。

また、好きに動かせるようv-sliderコンポーネントを入れて完成です。
200点:cherry_blossom::cherry_blossom::cherry_blossom:
sparkline5.gif

ところで焦点の内側に入ると実像が出来ていませんね。
このときは、虚像が出来るのですが今回は実装見送りです......

##ホスティング
最後にfirebaseにホスティングして終了です。
https://firebase.google.com/docs/hosting?authuser=0

$ firebase init hosting
$ firebase deploy --only hosting

##まとめ
凸レンズをふと思い出して作ってみました。
失敗もしましたがv-sparklineの使い方を学べたのでよしということで。

また、前回記事よりはまとまった記事にできた気がします。
今後もしょうもないことに取り組んでVueと仲良くなっていきたいです。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2