突然ですが、こんな図みたことありますよね?
これは理科で定番の凸レンズの焦点に関する問題です。
今回はこの焦点問題を理解しつつ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
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
##焦点問題とは
ざっくりいうと凸レンズに入射した光が集まる位置に実像ができる。
虫眼鏡でものが大きくみえる現象はこの原理を利用していたりする。
焦点は凸レンズに平行に入射した光が集まる場所
ちなみに、凸レンズの中心から焦点までの距離の2倍に光源があると光源と実像は同じ大きさになる。
解答のポイントは光の線を引きその交点を求めること。
- 光軸と平行な線
- 凸レンズの中心を通る線
- 焦点を通る線
##線を引く
解答のポイントとなる、光源から出る線を引く方法を考えます。
###vuetifyで作図
Vuetifyのドキュメントを眺めているとグラフ作成用UIコンポーネントがありました。
v-sparkline
なんとなくおしゃれだしこれで。
まずはサンプルをcomposition-apiを使いつつコンポーネント化します。
<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>
焦点問題の作図をイメージして、3本の線を引きます。
線を重ねるためにpositionを設定しています。
<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>
いい感じ......
あれ、他要素との座標をどうやって合わせる......?
ただ単純に用意した画像を重ねると以下の通り、これでは0点です。
ここからどうするか......
ごまかしでwidthやheightを調整するうちに心が痛くなったので別の手段を考えます
###svgで表現する
図形を組み合わせながら問題を用意します。
<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>
いよいよ作図します。
線を描画する関数、実像を描画する関数を用意しひとまず手計算した値を渡します。
/**
* 線を描画
* @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);
}
/**
* 四角形を描画
* @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);
}
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]);
});
##自動化
もちろん手計算は面倒です。
交点を算出する関数を用意し、vueファイルとは別に切り出します。
計算ばかりなので興味ある方はソースコードを参照ください。
また、好きに動かせるようv-sliderコンポーネントを入れて完成です。
200点
ところで焦点の内側に入ると実像が出来ていませんね。
このときは、虚像が出来るのですが今回は実装見送りです......
##ホスティング
最後にfirebaseにホスティングして終了です。
https://firebase.google.com/docs/hosting?authuser=0
$ firebase init hosting
$ firebase deploy --only hosting
##まとめ
凸レンズをふと思い出して作ってみました。
失敗もしましたがv-sparklineの使い方を学べたのでよしということで。
また、前回記事よりはまとまった記事にできた気がします。
今後もしょうもないことに取り組んでVueと仲良くなっていきたいです。