//--Class 1/1 WebBasic
class WebBasic {
constructor( app ) {
console.log( "webbasic constructor" );
this.isFF = window.navigator.userAgent.match( /firefox/i ) != null;
this.app = app;
//check.
if( typeof this.app.debug === "undefined" )
this.app.debug = new Object();
//check. canvas contextに独自拡張する場所を設ける
if( typeof this.app.cc.tmp === "undefined" )
this.app.cc.tmp = new Object();
this.cc = this.app.cc;
let canvas = this.app.cc.canvas;
canvas.tmp = new Object();
canvas.tmp.app = this;
this.csoc = window.getComputedStyle( canvas ); //ex. computed style of canvas
let bp = this.getBP( canvas );
this.config = {
maximize : false,
keepAspectRatio : false,
stretchWidth : true,
stretchHeight : true,
widthAsPer : 0,
heightAsPer : 0,
zoom : 1,
pixelWidth : 1,
pixelHeight : 1,
antiAlias1 : true,
antiAlias2 : true,
resizeObserver : true,
}
this.initial = {
width : this.cc.canvas.width,
height : this.cc.canvas.height,
styleWidth_inner : Number( this.csoc.width.replace( /px/, "" ) ),
styleHeight_inner : Number( this.csoc.height.replace( /px/, "" ) ),
styleWidth_outer : Number( this.csoc.width.replace( /px/, "" ) ),
styleHeight_outer : Number( this.csoc.height.replace( /px/, "" ) ),
aspectRatio : 1,
}
//tweak. 追加の調整
let cb = this.csoc.boxSizing == "content-box";
let bb = this.csoc.boxSizing == "border-box";
this.initial.styleWidth_inner -= bb ? bp.L + bp.R : 0;
this.initial.styleHeight_inner -= bb ? bp.T + bp.B : 0;
this.initial.styleWidth_outer += cb ? bp.L + bp.R : 0;
this.initial.styleHeight_outer += cb ? bp.T + bp.B : 0;
this.initial.aspectRatio = this.initial.styleWidth_inner / this.initial.styleHeight_inner;
//canvasタグに設定されたdata属性をスクリプトとして実行
if( typeof canvas.dataset.webbasic != "undefined" ) {
eval( canvas.dataset.webbasic.replace( /
/gi, "\n" ) );
}
//---■ ResizeObserver を使用できるかどうか
if( this.config.resizeObserver
&& typeof ResizeObserver !== "undefined" ) {
this.resizeObserver = new ResizeObserver(
//---【ResizeObserver】
//-- 要素がリサイズされたときの処理 --
function( entries ) {
console.log( "webbasic ResizeObserver" );
/* memo.
このコールバックが繰り返し呼ばれる原因
要素がリサイズしたことで呼ばれるのと、
リサイズしたことにより、ウィンドウのスクロールバーが
出現し、それによってさらにリサイズが起こって呼ばれる場合
*/
//check. 縦横ともに%ではない指定(固定値)の場合、何もしない
if( ! this.config.maximize
&& ! this.config.widthAsPer
&& ! this.config.heightAsPer ) {
//サイズの変更はしない
return;
}
//check. 重複防止
if( this.mayOverlap ) { //ex. 親要素をきっかけにすでに実行された
this.mayOverlap = false;
return;
}
console.log( "ResizeObserver" );
//どの要素のリサイズなのか(canvasが優先)
let launchBy = this.cc.canvas.parentNode;
//this.cc.canvasがあるなら優先する
for( let entry of entries ) {
if( entry.target == this.cc.canvas ) {
launchBy = this.cc.canvas;
break;
}
}
if( this.updateCanvasSettings()
&& launchBy == this.cc.canvas.parentNode )
this.mayOverlap = true;
}.bind( this )
);
this.mayOverlap = true;
/*
ResizeObserverにより、親要素のリサイズで
this.updateCanvasSettings()が呼ばれ。
そのなかでcanvas要素のサイズが変更されると、
続いて同じthis.updateCanvasSettings()が呼ばれてしまう。
それを防止するためのフラグ。
*/
//監視開始
this.resizeObserver.observe( this.cc.canvas );
//canvasが%指定のとき発動
this.resizeObserver.observe( this.cc.canvas.parentNode );
//canvasがmaximize指定、widthAsPer指定のとき発動
} else {
//ResizeObserverが使えない場合は、ウィンドウリサイズのみでの検知となる
// addEventListener( "resize", this.updateCanvasSettings.bind( this ), false );
addEventListener( "resize", function( e ) {
this.updateCanvasSettings();
}.bind( this ), false );
}
}//WebBasic constructor()
//-- Method getBP (WebBasic 1/6)
//BP is border and padding size at left, top, right, bottom
getBP( element ) {
let computedStyle = window.getComputedStyle( element );
//style.borderWidth
let blw = Number( computedStyle.borderLeftWidth.replace( /px/, "" ) );
let btw = Number( computedStyle.borderTopWidth.replace( /px/, "" ) );
let brw = Number( computedStyle.borderRightWidth.replace( /px/, "" ) );
let bbw = Number( computedStyle.borderBottomWidth.replace( /px/, "" ) );
//style.padding
let pl = Number( computedStyle.paddingLeft.replace( /px/, "" ) );
let pt = Number( computedStyle.paddingTop.replace( /px/, "" ) );
let pr = Number( computedStyle.paddingRight.replace( /px/, "" ) );
let pb = Number( computedStyle.paddingBottom.replace( /px/, "" ) );
//ex. bpL is border,padding left
let bpL = blw + pl;
let bpT = btw + pt;
let bpR = brw + pr;
let bpB = bbw + pb;
return {
L : bpL,
T : bpT,
R : bpR,
B : bpB,
}
}//getBP()
//-- Method start (WebBasic 2/6)
start() {
console.log( "webbasic start" );
this.updateCanvasSettings();
this.app.start();
this.app.draw( this.cc );
}
//-- Method stop (WebBasic 3/6)
stop() {
console.log( "webbasic stop" );
this.app.stop();
}
//-- Method draw (WebBasic 4/6)
draw( cc ) {
this.app.draw( cc );
}
//-- Method updateCanvas (WebBasic 5/6)
updateCanvas() {
this.updateCanvasSettings();
this.draw( this.cc );
}
//-- Method updateCanvasSettings (WebBasic 6/6)
updateCanvasSettings( e ) {
/*
このメソッドは、大きく3つのセクションに分かれている
§1 現状の状態、条件を把握
§2 現状の状態と条件から新しい値を算出
§3 新しい値を適用
*/
// console.log( "--updateCanvasSettings()--" );
let _kiseki = "start";
let kiseki = function( str ) {
_kiseki += " ▶ " + str;
}
//§1 現状の状態、条件を把握
kiseki( "§1" );
let canvas = this.app.cc.canvas;
let cc = this.app.cc;
let bp = this.getBP( canvas );
//ex. bp is style.[b]orderWidth + style.[p]adding
let isContentBox = this.csoc.boxSizing == "content-box";
let isBorderBox = this.csoc.boxSizing == "border-box";
//設定値不正の修正
if( this.config.zoom <= 0 ) this.config.zoom = 1;
if( this.config.pixelWidth <= 0 ) this.config.pixelWidth = 1;
if( this.config.pixelHeight <= 0 ) this.config.pixelHeight = 1;
//そのままの変数名だと長いので略語を定義
let ISWI = this.initial.styleWidth_inner;
let ISHI = this.initial.styleHeight_inner;
let ISWO = this.initial.styleWidth_outer;
let ISHO = this.initial.styleHeight_outer;
let IAR = this.initial.aspectRatio;
let IW = this.initial.width;
let IH = this.initial.height;
let SHor = this.config.stretchHorizontally;
let SVer = this.config.stretchVertically;
let ZM = this.config.zoom;
let MX = this.config.maximize;
let KAR = this.config.keepAspectRatio;
//ex. WasP is width as per.
let WasP = MX ? 100 : this.config.widthAsPer;
let HasP = MX ? 100 : this.config.heightAsPer;
//対象は画面に表示されているか?
let crr = canvas.getBoundingClientRect();
let isActive = crr.top + crr.height / 2 >= 0 && crr.top + crr.height / 2 <= window.innerHeight;
console.log( isActive );
//§2 現状の状態と条件から新しい値を算出
kiseki( "§2" );
//新しい値を収めるオブジェクト
let c2b = new Object(); //ex. canvas to be
c2b.width = IW;
c2b.height = IH;
let cs2b = new Object(); //ex. canvas.style to be
let units = new Object();
units.width = "px";
units.height = "px";
/* memo.
下記で canvas.width, height を変更(というより「代入」)すると、
canvasの仕様で、画面がクリアされる。
そのため、リサイズ時にちらついて見える。
*/
let p = canvas.parentNode;
let p_bp = this.getBP( p ); //ex. parent's border & padding
let pr = p.getBoundingClientRect(); //ex. parent's rect.
//--- if( WasP || HasP )
if( WasP || HasP ) {
//WasPとHasPいずれか%指定しているとき
kiseki( "if(WasP||HasP)" );
//%指定はどこに対する%なのか。そしてその対象のサイズを得る
let tw, th; //ex. target width
let pw = pr.width - p_bp.L - p_bp.R;
let ph = pr.height - p_bp.T - p_bp.B;
let ww = document.body.parentNode.clientWidth;
let wh = document.body.parentNode.clientHeight;
//check. firefoxは2ドット多い
// if( this.isFF ) wh -= 2;
/* memo.
ルート要素の clientHeight は MDN によれば、
「特例で、スクロールバーを除いたビューポートの高さ」。
*/
//親の横幅がウィンドウの横幅を超えるなら、
//ウィンドウの横幅を対象、そうでなければ親要素の横幅を対象
tw = pw > ww ? ww : pw;
//親の縦幅がウィンドウの縦幅を超えるなら、
//ウィンドウの縦幅を対象、そうでなければ親要素の縦幅を対象
th = ph > wh ? wh : ph;
//そのパーセント指定をピクセル指定に直した値
let pixedWasP = WasP / 100 * tw;
let pixedHasP = HasP / 100 * th;
//tweak. pixedWasP, pixedHasP は border を含まない、とする。
if( isBorderBox ) {
pixedWasP -= bp.L + bp.R;
pixedHasP -= bp.T + bp.B;
}
let type = 0;
//わかりやすくするために冗長な条件文にした
if( WasP && HasP && KAR && tw / th < IAR ) type = 1;
else if( WasP && HasP && KAR && tw / th >= IAR ) type = 2;
else if( WasP && HasP && ! KAR ) type = 4;
else if( WasP && ! HasP ) type = 5;
else if( ! WasP && HasP ) type = 6;
//debug.
this.app.debug.type = type;
console.log( "type:", type );
//見やすいように、
//下記▲は canvas.style.width, height を設定してる箇所を示す
//下記●は canvas..width, height を設定してる箇所を示す
//--- switch( type )
kiseki( "switch(" + type + ")" );
switch( type ) {
//-- アスペクト比を保ちながら、可能な限り指定の拡大を行う --
case 1: //その、横方向にフィットする場合
// console.log( "yoko" );
//▲canvas.style.width,height
cs2b.width = pixedWasP;
cs2b.height = "keepAspectRatio";
//●canvas.width,height
if( ! SHor && ! SVer ) {
c2b.width = pixedWasP;
c2b.height = "keepAspectRatio";
} else {
if( ! SHor ) c2b.width = pixedWasP;
if( ! SVer ) c2b.height = pixedHasP;
}
break;
case 2: //その、縦方向にフィットする場合
//type 2
// console.log( "tate" );
//▲canvas.style.width,height
cs2b.width = "keepAspectRatio";
cs2b.height = pixedHasP;
//●canvas.width,height
if( ! SHor && ! SVer ) {
c2b.width = "keepAspectRatio";
c2b.height = pixedHasP;
} else {
if( ! SHor ) c2b.width = pixedWasP;
if( ! SVer ) c2b.height = pixedHasP;
}
break;
//-- アスペクト比を崩し、指定の拡大を行う --
case 4:
//▲canvas.style.width, height
cs2b.width = pixedWasP;
cs2b.height = pixedHasP;
//●canvas.width,height
if( ! SHor ) c2b.width = pixedWasP;
if( ! SVer ) c2b.height = pixedHasP;
break;
//-- 縦横いずれか指定の拡大を行う。アスペクト比は設定による --
case 5: //その、横の場合
//▲canvas.style.width, height
cs2b.width = pixedWasP;
cs2b.height = KAR ? "keepAspectRatio" : ISHI;
//●canvas.width,height
if( ! SHor ) c2b.width = pixedWasP;
if( KAR && ! SVer ) {
let rate = cs2b.width / IW;
c2b.height = IH * rate;
}
break;
case 6: //その、縦の場合
//▲canvas.style.width, height
cs2b.width = KAR ? "keepAspectRatio" : ISWI;
cs2b.height = pixedHasP;
//●canvas.width,height
if( KAR && ! SHor ) {
let rate = cs2b.height / IH;
c2b.width = IW * rate;
}
if( ! SVer ) c2b.height = pixedHasP;
break;
}//switch
//tweak. "keepAspectRatio" 指定を数値にする(style)
if( cs2b.width == "keepAspectRatio" ) {
kiseki( "tweak.styWidth=='keepAspectRatio'" );
/* memo.
資料
IW,IHはcanvas.width,heightであり、枠を含まない
tw,thは親の内側のサイズ
ここでのサイズ計算結果は枠を含まない、としている。
*/
let rate = cs2b.height / IH;
cs2b.width = IW * rate;
//check. 親の幅を超過する
if( cs2b.width > tw ) {
cs2b.width = tw;
rate = cs2b.width / IW;
cs2b.height = IH * rate;
//tweak. 枠を考慮
cs2b.width -= bp.L + bp.R;
cs2b.height -= bp.T + bp.B;
}
} else if( cs2b.height == "keepAspectRatio" ) {
kiseki( "tweak.styHeight=='keepAspectRatio'" );
let rate = cs2b.width / IW;
cs2b.height = IH * rate;
//check. 親の高さを超過する
if( cs2b.height > th ) {
cs2b.height = th;
rate = cs2b.height / IH;
cs2b.width = IW * rate;
//tweak. 枠を考慮
cs2b.width -= bp.L + bp.R;
cs2b.height -= bp.T + bp.B;
}
}/* else if( cs2b.height == "keepAspectRatio" ) {
let rate = cs2b.width / IW;
cs2b.height = IH * rate;
}*/
}//if wasp || hasp
kiseki( "after if(wasp||hasp)" );
//新しい値について調整
//tweak. border-box の場合はボーダーとパディングをサイズに含む
if( isBorderBox ) {
cs2b.width += bp.L + bp.R;
cs2b.height += bp.T + bp.B;
}
//tweak. "keepAspectRatio" 指定を数値にする(属性)
if( c2b.width == "keepAspectRatio" ) {
let rate = c2b.height / IH;
c2b.width = IW * rate;
} else if( c2b.height == "keepAspectRatio" ) {
let rate = c2b.width / IW;
c2b.height = IH * rate;
}
//tweak. config.zoomを適用
if( ZM != 1 ) {
c2b.width /= ZM;
c2b.height /= ZM;
}
//canvas.width, height の代替
cc.tmp.width = c2b.width ? c2b.width *ZM : ( IW / ZM );
cc.tmp.height = c2b.height ? c2b.height *ZM : ( IH / ZM );
//ピクセル関連
//ピクセル関連 - モザイク
let pixelWidthZM = this.config.pixelWidth / ZM;
let pixelHeightZM = this.config.pixelHeight / ZM;
if( pixelWidthZM != 1 || pixelHeightZM != 1 ) {
kiseki( "if(pixelWidthZM!=1||..)" );
//check. styleのサイズが未定義の場合、定義する必要
if( ! cs2b.width || ! cs2b.height ) {
cs2b.width = c2b.width * ZM;
cs2b.height = c2b.height * ZM;
} else {
// cs2b.width *= ZM;
// cs2b.height *= ZM;
}
console.log( cs2b.width, cs2b.height );
c2b.width /= pixelWidthZM;
c2b.height /= pixelHeightZM;
/* memo.
canvasの属性 width, height が変更されると
canvasはstyle以外全リセットされるので、再設定の必要がある。
(cc.scale()を再設定)
*/
}
//ピクセル関連 - アンチエイリアス
if( ! this.config.antiAlias1 ) {
canvas.style.imageRendering = "pixelated";
canvas.style.imageRendering = "optimizeSpeed";
//仕様によりこの2行の連続で良い
}
//tweak.
if( c2b.width ) c2b.width = Math.round( c2b.width );
if( c2b.height ) c2b.height = Math.round( c2b.height );
//§3 新しい値を適用
kiseki( "§3" );
//変化の検知用
let beforeStyleWidth = this.csoc.width; //ex. computed style of canvas
let beforeStyleHeight = this.csoc.height;
//return時に使用される
//canvas.style を更新
for( let name in cs2b ) {
//単位が必要な場合、必要ない場合
if( units[ name ] )
canvas.style[ name ] = cs2b[ name ] + units[ name ];
else
canvas.style[ name ] = cs2b[ name ];
}
//canvas属性 を更新
let sizeChanged = false;
for( let name in c2b ) {
//check. 同じ値の代入は行わない
if( canvas[ name ] == c2b[ name ] ) continue;
//width, height は「代入」されると
//それだけで canvas がリセットされてしまうから
//無用なリセットを避ける
canvas.setAttribute( name, c2b[ name ] );
//check.
if( name == "width" || name == "height" ) {
sizeChanged = true; //上行とは別件
}
}
//check. サイズ変更 = canvas リセット なので
if( sizeChanged ) {
kiseki( "if(sizeChanged)" );
//scale()設定
cc.scale( 1 / pixelWidthZM, 1 / pixelHeightZM );
console.log( "scaled", 1 / pixelWidthZM, 1 / pixelHeightZM );
//SVGフィルタ設定
if( ! this.config.antiAlias2
&& document.getElementById( "filterElement" ) == null ) {
let svg = document.createElement( "svg" );
document.body.appendChild( svg );
svg.outerHTML = "\
\
";
cc.filter = 'url(#removeAlpha)';
}//if
}//if
//check.
if( 0 )
if( isActive ) {
crr = canvas.getBoundingClientRect();
let scY = window.scrollY + crr.top;
scY -= ( window.innerHeight - crr.height ) / 2;
window.scrollTo( 0, scY );
}
//サイズ(style)が変更されているなら true、変更されていないなら false
let result =
beforeStyleWidth != this.csoc.width
|| beforeStyleHeight != this.csoc.height;
console.log( "kiseki", _kiseki );
return result;
}//updateCanvasSettings()
}//WebBasic