view source
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 'use strict'
/**
* SVGのベジェ曲線
*/
class SVGBezierCurvie {
/**
* @param {SVGCanvas} canvas
* @param p1 始点
* @param cp1 制御点1
* @param cp2 制御点2
* @param p2 終点
*/
constructor(canvas, p1, cp1, cp2, p2) {
this
.canvas = canvas;
this
.path =
this
.createPath(p1, cp1, cp2, p2);
this
.auxiliaryLine1 =
this
.createAuxiliaryLine(p1, cp1);
//補助線1
this
.auxiliaryLine2 =
this
.createAuxiliaryLine(p2, cp2);
//補助線1
this
.p1 =
this
.createPoint(p1.x, p1.y);
//始点
this
.cp1 =
this
.createPoint(cp1.x, cp1.y);
//制御点1
this
.cp2 =
this
.createPoint(cp2.x, cp2.y);
//制御点2
this
.p2 =
this
.createPoint(p2.x, p2.y);
//終点
}
/**
* 点を作成する
* @param {number} x 中心のX座標
* @param {number} y 中心のY座標
*/
createPoint(x, y) {
circle.cx.baseVal.value = x;
circle.cy.baseVal.value = y;
circle.r.baseVal.value = 10;
circle.onMoved = () => {
this
.updateAuxiliaryLine();
this
.updatePath();
};
this
.canvas.appendChild(circle);
return
circle;
}
/**
* 補助線を作成する
* @param p1 始点
* @param p2 終点
*/
createAuxiliaryLine(p1, p2) {
line.x1.baseVal.value = p1.x;
line.y1.baseVal.value = p1.y;
line.x2.baseVal.value = p2.x;
line.y2.baseVal.value = p2.y;
line.setAttribute(
'stroke'
,
'gray'
);
line.setAttribute(
'stroke-width'
, 5);
line.setAttribute(
'stroke-dasharray'
, 5);
this
.canvas.appendChild(line);
return
line;
}
createPath(p1, cp1, cp2, p2) {
const pathData = [
{ type:
"M"
, values: [p1.x, p1.y] },
{ type:
"C"
, values: [cp1.x, cp1.y, cp2.x, cp2.y, p2.x, p2.y] },
];
path.setPathData(pathData);
path.setAttribute(
'stroke'
,
'blue'
);
path.setAttribute(
'stroke-width'
, 5);
path.style.fillOpacity = 0;
this
.canvas.appendChild(path);
return
path;
}
updateAuxiliaryLine() {
this
.auxiliaryLine1.x1.baseVal.value =
this
.p1.cx.baseVal.value;
this
.auxiliaryLine1.y1.baseVal.value =
this
.p1.cy.baseVal.value;
this
.auxiliaryLine1.x2.baseVal.value =
this
.cp1.cx.baseVal.value;
this
.auxiliaryLine1.y2.baseVal.value =
this
.cp1.cy.baseVal.value;
this
.auxiliaryLine2.x1.baseVal.value =
this
.cp2.cx.baseVal.value;
this
.auxiliaryLine2.y1.baseVal.value =
this
.cp2.cy.baseVal.value;
this
.auxiliaryLine2.x2.baseVal.value =
this
.p2.cx.baseVal.value;
this
.auxiliaryLine2.y2.baseVal.value =
this
.p2.cy.baseVal.value;
}
updatePath() {
const pathData = [
{ type:
"M"
, values: [
this
.p1.cx.baseVal.value,
this
.p1.cy.baseVal.value] },
{
type:
"C"
, values: [
this
.cp1.cx.baseVal.value,
this
.cp1.cy.baseVal.value,
this
.cp2.cx.baseVal.value,
this
.cp2.cy.baseVal.value,
this
.p2.cx.baseVal.value,
this
.p2.cy.baseVal.value
]
},
];
this
.path.setPathData(pathData);
}
}
class SVGCanvas {
/**
* @param {SVGSVGElement} svg_id SVG要素のID
*/
constructor(svg_id) {
this
.dragElem =
null
;
this
.offsetX = 0;
this
.offsetY = 0;
this
.svg = document.getElementById(svg_id);
this
.svg.addEventListener(
"mousemove"
, e =>
this
.mouseMove(e),
false
);
this
.svg.addEventListener(
"touchmove"
, e =>
this
.mouseMove(e),
false
);
this
.svg.addEventListener(
"mouseup"
, e =>
this
.mouseUp(e),
false
);
this
.svg.addEventListener(
"touchend"
, e =>
this
.mouseUp(e),
false
);
}
/**
*ベジェ曲線を追加する
* @param p1 始点
* @param cp1 制御点1
* @param cp2 制御点2
* @param p2 終点
*/
addBezierCurvie(p1, cp1, cp2, p2) {
const bezierCurvie =
new
SVGBezierCurvie(
this
.svg, p1, cp1, cp2, p2);
this
.setDragEvent(bezierCurvie.p1);
this
.setDragEvent(bezierCurvie.cp1);
this
.setDragEvent(bezierCurvie.cp2);
this
.setDragEvent(bezierCurvie.p2);
}
/**
* SVGElementをドラッグ可能にする
* @param {SVGElement} elem ドラッグ可能要素
*/
setDragEvent(elem) {
elem.addEventListener(
"mousedown"
, e =>
this
.mouseDown(e),
false
);
elem.addEventListener(
"touchstart"
, e =>
this
.mouseDown(e),
false
);
elem.addEventListener(
"mousemove"
, e =>
this
.mouseMove(e),
false
);
elem.addEventListener(
"touchmove"
, e =>
this
.mouseMove(e),
false
);
elem.addEventListener(
"mouseup"
, e =>
this
.mouseUp(e),
false
);
elem.addEventListener(
"touchend"
, e =>
this
.mouseUp(e),
false
);
}
/**
* @param {string} line_id 補助線のID
*/
addLine(line_id) {
this
.line = document.getElementById(line_id);
}
/**
* 画面座標(x,y)をSVG座標に変換する
* @param {SVGSVGElement} svg SVG要素
* @param {SVGSVGElement} elem
* @param {number} x
* @param {number} y
* @return {SVGPoint} SVG上の座標
*/
screenPointToSVGPoint(svg, elem, x, y) {
const p = svg.createSVGPoint();
p.x = x;
p.y = y;
const CTM = elem.getScreenCTM();
return
p.matrixTransform(CTM.inverse());
}
mousePointToSVGPoint(e) {
return
this
.screenPointToSVGPoint(
this
.svg,
this
.dragElem,
e.clientX,
e.clientY);
}
/**
* @param {MouseEvent} e
*/
mouseDown(e) {
const event = (e.type ===
"mousedown"
) ? e : e.changedTouches[0];;
this
.dragElem = event.target;
const p =
this
.mousePointToSVGPoint(event);
this
.offsetX = p.x -
this
.dragElem.getAttribute(
"cx"
);
this
.offsetY = p.y -
this
.dragElem.getAttribute(
"cy"
);
event.preventDefault();
}
/**
* @param {MouseEvent} e
*/
mouseUp(e) {
this
.dragElem =
null
;
}
/**
* @param {MouseEvent} e
*/
mouseMove(e) {
if
(!
this
.dragElem) {
return
; }
const event = (e.type ===
"mousemove"
) ? e : e.changedTouches[0];
const p =
this
.mousePointToSVGPoint(event);
this
.dragElem.cx.baseVal.value = p.x -
this
.offsetX;
this
.dragElem.cy.baseVal.value = p.y -
this
.offsetY;
this
.dragElem.onMoved();
event.preventDefault();
}
}
function
init() {
const canvas =
new
SVGCanvas(
'svg'
);
canvas.addBezierCurvie(
{ x: 0, y: 0 },
{ x: 300, y: 100 },
{ x: 100, y: 300 },
{ x: 400, y: 400 });
outputXY();
}
var
outputXY =
function
(){
$(
function
(){
$(
'circle'
).each(
function
(){
var
x = $(
this
).attr(
'cx'
);
var
y = $(
this
).attr(
'cy'
);
var
input = $(
'<input>'
).val(x+
':'
+y);
$(
'#svg'
).after(input);
});
$(
'#svg'
).on(
'mousemove'
,
function
(){
$(
'circle'
).each(
function
(index){
var
x = $(
this
).attr(
'cx'
);
var
y = $(
this
).attr(
'cy'
);
$(
'input'
).eq(index).val(x+
':'
+y);
});
});
});
}
init();
CSS
123456789101112131415161718 #svgWrap{
width
:
420px
;
margin
:
auto
;
}
#svg {
border
:
4px
solid
#eee
;
display
:
block
;
margin
:
auto
;
}
circle
{
cursor
:
move
;
}
input{
display
:
block
;
font-size
:
22px
;
}
HTML
ページのソースを表示 : Ctrl+U , DevTools : F12
view-source:https://hi0a.com/demo/-svg/svg-path-bezier/
ABOUT
hi0a.com 「ひまアプリ」は無料で遊べるミニゲームや便利ツールを公開しています。
プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。
必要な機能の関数をコピペ利用したり勉強に活用できます。
プログラムの動作サンプル結果は画面上部に出力表示されています。
環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。
画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。
動く便利なものが好きなだけで技術自体に興味はないのでコードは汚いです。
途中放置や実験状態、仕様変更、API廃止等で動かないページもあります。
view source
JavaScript
CSS
HTML
ABOUT
hi0a.com 「ひまアプリ」は無料で遊べるミニゲームや便利ツールを公開しています。
プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。
必要な機能の関数をコピペ利用したり勉強に活用できます。
プログラムの動作サンプル結果は画面上部に出力表示されています。
環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。
画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。
動く便利なものが好きなだけで技術自体に興味はないのでコードは汚いです。
途中放置や実験状態、仕様変更、API廃止等で動かないページもあります。