//--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