/* */ window.alertTMP = window.alert; window.alert = function() { let str = ""; if( arguments[ 0 ] instanceof Object ) { for( let name in arguments[ 0 ] ) { str += name + " = " + arguments[ 0 ][ name ] + "\n"; } } else { str = arguments[ 0 ]; for( let i = 1; i < arguments.length; i++ ) { str += ", " + arguments[ i ]; } } window.alertTMP( str ); } addEventListener( "load", function() { //---以下 naviのstyle let st = document.createElement( "style" ); //下記のコメント内では、/**/を/++/で記載し、あとから置換する。 st.innerHTML = function() {/* #hskdiv { background-color : yellow; } /+ ex. 'hit space key' div +/ #navi { width : 100%; } #navichartd { } #navicharimg { height : 25vh; /+キャラ画像高さ変更はここと、各@mediaにて+/ } #navihuki { width : 100%; left : -1em; font-size : 3vh; } /+ 1: とても縦長なデバイス用 アスペクト比~0.5 +/ /+ 横1に対し縦2以上 +/ @media screen and ( max-aspect-ratio: 1/2 ) { #hskdiv { background-color : red; } #navi { width : 100%; } #navichartd { } #navicharimg { height : 24vw; /+キャラ画像高さ変更はここ+/ } #navihuki { width : 100%; left : 0em; font-size : 1.8vh; } } /+ 2: 縦長なデバイス用 アスペクト比0.5~1 +/ /+ 横1に対し縦1~2 +/ @media screen and ( min-aspect-ratio: 1/2 ) and ( max-aspect-ratio: 1/1 ) { #hskdiv { background-color : green; } #navi { width : 100%; } #navichartd { } #navicharimg { height : 24vw; /+キャラ画像高さ変更はここ+/ } #navihuki { width : 100%; font-size : 2vh; } } /+ 3: 正方形~4:3なデバイス用 アスペクト比1~1.33 +/ /+ 横1に対し縦1~2 +/ @media screen and ( min-aspect-ratio: 1/1 ) and ( max-aspect-ratio: 4/3 ) { #hskdiv { background-color : blue; } #navi { width : 100%; } #navichartd { } #navicharimg { height : 24vh; /+キャラ画像高さ変更はここ+/ } #navihuki { width : 100%; font-size : 3vh; } } /+ 4: 4:3~横長なデバイス用 アスペクト比1.33~2 +/ @media screen and ( min-aspect-ratio: 4/3 ) and ( max-aspect-ratio: 2/1 ) { #hskdiv { background-color : orange; } #navi { width : 100%; } #navichartd { } #navicharimg { height : 25vh; /+キャラ画像高さ変更はここ+/ } #navihuki { width : 100%; font-size : 3vh; } } /+ 5: とても横長なデバイス用 アスペクト比2~ +/ @media screen and ( min-aspect-ratio: 2/1 ) { #hskdiv { background-color : magenta; } #navi { width : 100%; } #navichartd { } #navicharimg { height : 28.5vh; /+キャラ画像高さ変更はここ+/ } #navihuki { width : 100%; font-size : 3.5vh; } } */} .toString() .match( /\/\*([\s\S]*)\*\// )[ 1 ] .replace( /\/\+/g, "/*" ) .replace( /\+\//g, "*/" ); document.getElementsByTagName( "head" )[ 0 ].appendChild( st ); } );//addEventListener "load" //---以上 naviのstyle //---関数srcnavi async function srcnavi( data, seek, seek2, place ) { /* この引数の data とは、メッセージを記述したオブジェクトのことです。 */ // 現在 どうする // 動作中 終了 // 動作終了 新規開始 if( typeof window.timeline !== "undefined" ) { //動作中 → 終了する timeline.destroy(); delete window.timeline; } else { //動作終了中 → 新規開始する document.activeElement.blur(); //リンクなどのフォーカスを除去しないと //動作中にEnterキーを押された場合に //リンクをクリックしたことになり、 //この関数が呼ばれ、終了してしまう。 //check. if( typeof seek === "undefined" ) seek = 0; if( typeof seek2 === "undefined" ) seek2 = 0; timeline = new Timeline( data ); timeline.scrollYBak = scrollY; timeline.start( seek, seek2, place ); } return new Promise( function( tellOk ) { tellOk(); } ); } function delpx( str ) { return Number( str.replace( /px$/, "" ) ); } //--class timeline addEventListener( "load", function() { Timeline.images = new Object(); //1つの画像のローダ let loadIt = function( imgsrc ) { imgsrc.match( /\/([^/]+?)\/([^/]+?)\.png/ ); let subdir = RegExp.$1; let name = RegExp.$2; //check. if( subdir == "_img" ) subdir = ""; return new Promise( function( tellOk ) { //ロード開始 console.log( "loading..", subdir, name ); let image = new Image(); image.onload = tellOk; image.src = imgsrc; } ).then( function( e ) { //ロード完了後 console.log( "loaded:", name ); return { subdir : subdir, name : name, image : e.target, }; //この返値オブジェクトが下記returnsの要素になる } ); }//func loadIt Promise.all( [ loadIt( "/srcnavi/_img/pointingHand.png" ), loadIt( "/srcnavi/_img/kuchipaku/a.png" ), loadIt( "/srcnavi/_img/kuchipaku/i.png" ), loadIt( "/srcnavi/_img/kuchipaku/u.png" ), loadIt( "/srcnavi/_img/kuchipaku/e.png" ), loadIt( "/srcnavi/_img/kuchipaku/o.png" ), ] ).then( function( returns ) { let images = Timeline.images; //すべての画像読込後、結果をimagesに登録 for( let obj of returns ) { if( obj.subdir ) { let subdir = obj.subdir; //check. if( typeof images[ subdir ] === "undefined" ) { images[ subdir ] = new Object(); } images[ subdir ][ obj.name ] = obj.image; } else { images[ obj.name ] = obj.image; } } console.log( "ok2." ); if( typeof srcnaviStartsWhenReady != "undefined" ) { if( srcnaviStartsWhenReady ) srcnavi( navidata ); } }.bind( this ) ); } ); let timelinetmp = new Object(); class Timeline { constructor( data ) { //debug. if( 1 ) { this.div = document.createElement( "div" ); this.div.style.position = "absolute"; this.div.style.width = "100px"; this.div.style.height = "100px"; this.div.style.border = "solid 2px red"; this.div.style.boxSizing = "border-box"; document.body.appendChild( this.div ); } let promise; //---ua let ua = navigator.userAgent; this.isSmart = ua.indexOf('iPhone') > 0 || ua.indexOf('Android') > 0 && ua.indexOf('Mobile') > 0 || ua.indexOf('iPad') > 0 || ua.indexOf('Android') > 0 || ua.indexOf('Windows Phone') > 0 this.isPersonal = ! this.isSmart; this.data = data; this.seek = 0; this.seek2 = 0; this.seekAtEndOfPart = 0; this.place = 0; this.placeBak = ""; this.timerMs = 50; this.endflg = false; //---hit space key. create let hitspacekeyDiv = document.createElement( "div" ); hitspacekeyDiv.id = "hskdiv"; hitspacekeyDiv.style.display = "none"; hitspacekeyDiv.style.zIndex = 255; hitspacekeyDiv.style.position = "fixed"; hitspacekeyDiv.style.right = 0; hitspacekeyDiv.style.bottom = 0; hitspacekeyDiv.style.borderRadius = ".5em"; hitspacekeyDiv.style.paddingLeft = ".5em"; hitspacekeyDiv.style.paddingRight = ".5em"; // hitspacekeyDiv.style.backgroundColor = "white"; hitspacekeyDiv.style.fontSize = "2vw"; hitspacekeyDiv.style.fontWeight = "bolder"; hitspacekeyDiv.style.color = "white"; hitspacekeyDiv.innerHTML = "HIT SPACE KEY or CLICK or TAP"; document.body.appendChild( hitspacekeyDiv ); this.hitspacekeyDiv = hitspacekeyDiv; //ナビ部分コンテナ let navi; navi = document.createElement( "table" ); navi.id = "navi"; document.body.appendChild( navi ); navi.style.position = "fixed"; navi.style.bottom = "0"; navi.style.zIndex = 200; // navi.style.width = "100%"; // navi.style.maxHeight = "28%"; navi.innerHTML = function() {/* */}.toString().match( /\/\*([\s\S]*)\*\// )[ 1 ]; //---navi, huki, char this.navi = document.getElementById( "navi" ); this.huki = document.getElementById( "navihuki" ); this.char = document.getElementById( "navicharimg" ); this.char.ondblclick = async function( e ) { alert( "[もとの位置に戻り、終了します]" ); await this.setScrollAnimation( this.scrollYBak, 2000 ); this.destroy(); }.bind( this ); this.testflg = false; this.targets = new Array(); this.targetsBak = new Array(); this.targetsStyleBak = new Array(); this.targetsStyleBak2 = new Array(); //---marker create let mk = document.createElement( "div" ); mk.id = "marker"; mk.style.backgroundColor = "rgb(255,192,192)"; // mk.style.border = "solid 4px red"; mk.style.boxSizing = "border-box"; mk.style.position = "absolute"; mk.style.zIndex = 0; mk.style.borderRadius = ".5em"; document.body.appendChild( mk ); this.marker = mk; //---pointer create let pointer = new Image(); document.body.appendChild( pointer ); pointer.id = "pointer"; this.pointer = document.getElementById( pointer.id ); pointer.style.position = "absolute"; pointer.style.zIndex = 197; pointer.style.display = 'none'; this.pointerPointX = 0.02; this.pointerPointY = 0.8; //各種イベントハンドラ //(順序を変えないこと) this.handlers = new Object(); //---resize this.timerId3 = 0; this.handlers.resize = function( e ) { //スマートホンブラウザのアドレスバー動き対応 //check. スクロール中に起きたresizeはスキップ if( this.isSmart && this.flickCTR.scrl ) { return true; //4,5 } console.log( "navi: resize" ); clearTimeout( this.timerId3 ); this.timerId3 = setTimeout( function() { //check. ウィンドウは不正なブランクを表示中 let body = document.body || document.documentElement; let pageHeight = body.scrollHeight; //ページ全体の長さ if( scrollY + this.innerHeight() > pageHeight ) { scrollTo( 0, pageHeight - this.innerHeight() ); //これで不正なブランクが消える※1 } this.calcAndApplyPosition( true ); }.bind( this ), 0 ); //誤動作防止のためsetTimeoutで実行※2 /* ※1 ウィンドウ横幅を狭くしてページを縦方向に伸ばして表示していた時、 ウィンドウの表示位置はかなり下のほうになる。 その状態でウィンドウ横幅を広くしたとき、 かなり下のほうの表示位置のままページを表示するので、 ブランクが表示される。 その状態で計算する(ウィンドウからの相対値を使い計算)する。その後あるタイミングでブラウザがブランクを消去しウィンドウの表示位置を変更すると、計算結果が実際とずれる。 scrollToを行うと、ブラウザはすぐにブランクを訂正する。 ※2 おそらく※1と似たようなからくりで、キャラにしゃべらせているメッセージについて、自動改行等が入ってレイアウトが変更されるとずれる、、のではと思い、 setTimeoutでタイミングをずらした。 */ return true; }.bind( this );//resize this.handlers.touchstart = function( e ) { clearTimeout( this.flickCTR.timerId1 ); removeEventListener( "resize", this.handlers.resize ); //0 for( let ct of e.changedTouches ) { if( [ "navicharimg", "navihuki" ].indexOf( ct.target.id ) > -1 ) { if( this.testflg ) this.char.ondblclick(); else this.testflg = true; } } }.bind( this ); this.handlers.touchend = function() { this.flickCTR.timerId1 = setTimeout( function() { //2,3 addEventListener( "resize", this.handlers.resize ); }.bind( this ), 50 );//フリック終了してからリサイズイベント有効までの間隔 this.testflg = false; }.bind( this ); this.handlers.scroll = function( e ) { clearTimeout( this.flickCTR.timerId2 ); this.flickCTR.scrl = true; this.flickCTR.timerId2 = setTimeout( function() { //1,6 this.flickCTR.scrl = false; }.bind( this ), 250 );//スクロール中の長さ }.bind( this ); this.flickCTR = { scrl : false, timerId1 : 0, timerId2 : 0, } //debug. if( 0 ) { let test = document.createElement("div"); document.body.appendChild( test ); test.style.position = "fixed"; test.style.left = 0; test.style.top = 0; test.style.width = "100px"; test.style.height = "4px"; test.style.backgroundColor = "red"; } this.beforeBoin = "…"; }//Timeline constructor() innerWidth() { return window.innerWidth; // return document.body.parentNode.clientWidth; } innerHeight() { return window.innerHeight; // return document.body.parentNode.clientHeight; } //--method test async test( not ) { //ex. number of times let rop = this.pointer.getBoundingClientRect(); let rot = this.utl_getElementsRect( this.targets ); let styleBak; { let csop = getComputedStyle( this.pointer ); styleBak = { left : csop.left, top : csop.top, } } let csop = getComputedStyle( this.pointer ); let anm = new Object(); anm.start = function() { return new Promise( function( tellOk ) { let timerLt = 2000 * not; let timerMs = 100; anm.timerId = setInterval( anm.frame, timerMs ); anm.value = 0; anm.endValue = Math.PI * 2 * not; let len = anm.endValue - anm.value; anm.step = len / ( timerLt / timerMs ); anm.tellOk = tellOk; }.bind( this ) ); }.bind( this ); anm.frame = function() { anm.value += anm.step; //check. if( anm.value >= anm.endValue ) { clearInterval( anm.timerId ); anm.tellOk(); } let rot = this.utl_getElementsRect( this.targets ); /* このタイミングで取得する理由 この時点でスクロール中の場合があるから。 getBounding..()はscrollYの相対値なので、 タイミング悪くスクロールが入るとズレる。 */ let rotLeft = rot.left; let rotTop = rot.top; let rotWidth = rot.width; let rotHeight = rot.height; //check. ターゲットがポインタよりも小さい if( 0 && rotWidth < rop.width * 2 ) { let add = rop.width / 5; rotLeft -= add; //左に広げる rotWidth += add * 2; //右に広げる //check. 左端を超える if( rotLeft < 0 ) { rotWidth += rotLeft; rotLeft = 0; } } if( rotHeight < rop.height * 2 ) { let add = rop.height / 3.5; rotTop -= add; //上に広げる rotHeight += add * 2; //下に広げる //check. 上端を超える if( rotTop < 0 ) { rotHeight += rotTop; rotTop = 0; } } let cx = rotLeft + rotWidth / 2; let cy = rotTop + rotHeight / 2; let x = Math.cos( anm.value ) * rotWidth / 2; let y = Math.sin( anm.value ) * rotHeight / 2; let adjustX = rop.width * this.pointerPointX; let adjustY = rop.height * this.pointerPointY; x = cx + x - adjustX; y = cy + y - adjustY; //check. 右端を超える if( x + rop.width > this.innerWidth() ) { //右寄せにする x = this.innerWidth() - rop.width; } this.pointer.style.left = scrollX + x + "px"; this.pointer.style.top = scrollY + y + "px"; //debug. ターゲット if( 0 ) { this.div.style.left = scrollX + rotLeft + "px"; this.div.style.top = scrollY + rotTop + "px"; this.div.style.width = rotWidth + "px"; this.div.style.height = rotHeight + "px"; } }.bind( this ); await anm.start(); } //--method start start( seek, seek2, place ) { this.seek = seek; this.seek2 = seek2; this.pointer.src = Timeline.images.pointingHand.src; if( typeof place !== "undefined" ) this.targetting( place ); //イベント設定 for( let name in this.handlers ) { addEventListener( name, this.handlers[ name ] ); } this.play(); }//start() //--method destroy destroy() { this.untarget(); if( this.navi && this.navi.parentNode ) { this.navi.parentNode.removeChild( this.navi ); } if( this.marker && this.marker.parentNode ) { this.marker.parentNode.removeChild( this.marker ); } if( this.pointer && this.pointer.parentNode ) { this.pointer.parentNode.removeChild( this.pointer ); } if( this.hitspacekeyDiv && this.hitspacekeyDiv.parentNode ) { this.hitspacekeyDiv.parentNode.removeChild( this.hitspacekeyDiv ); } //イベント解除 for( let name in this.handlers ) { removeEventListener( name, this.handlers[ name ] ); } delete window.timeline; }//destroy() //--method calcAndApplyPosition calcAndApplyPosition( doScroll, doAnimate, isSync ) { //check. if( this.targets.length == 0 ) { return; } let aspect = this.innerWidth() / this.innerHeight(); let rot = this.utl_getElementsRect( this.targets ); let rob = document.body.getBoundingClientRect(); //ex. rect of body let robeot; //ex. rect of beot //targetの基準(原点)となる要素を検索 let beot = this.targets[ 0 ].parentNode; //ex. base element of targets while( ! getComputedStyle( beot ) .position.match( /(absolute|relative)/ ) ) { beot = beot.parentNode; //check. if( beot.tagName == "BODY" ) break; } robeot = beot.getBoundingClientRect(); //ex. rect of beot //---pointer set let ptx, pty; if( aspect > 1 ) { //画面が横長の場合 this.pointer.style.width = null; this.pointer.style.height = "10vh"; pointer.style.minWidth = null; pointer.style.minHeight = "4vw"; } else { //画面が縦長の場合 pointer.style.minWidth = "10vh"; pointer.style.minHeight = null; this.pointer.style.width = "10vw"; this.pointer.style.height = null; } let ptr = this.pointer.getBoundingClientRect(); let adjustX = ptr.width * this.pointerPointX; let adjustY = ptr.height * this.pointerPointY; ptx = scrollX + rot.left + rot.width - adjustX; pty = scrollY + rot.top - adjustY; //check. pointerが右端を超えている if( ptx + ptr.width > rob.left + rob.width ) { ptx = rob.left + rob.width - ptr.width; } //this.pointer.style.width = ptr.width + "px"; this.pointer.style.left = ptx + "px"; this.pointer.style.top = pty + "px"; //---marker set let mkx, mky, mkw, mkh; let tweakX = scrollX + rob.left; //bodyのmarginとか let tweakY = scrollY + rob.top; let hp = 12; //ex. horizontal padding let vp = 4; //ex. vertical padding mkx = scrollX + rot.left - hp - tweakX; mky = scrollY + rot.top - vp - tweakY; mkw = rot.width + hp * 2; mkh = rot.height + vp * 2; //document.bodyを親とした場合の座標を求め… let dx = robeot.left - rob.left; let dy = robeot.top - rob.top; //座標の親とdocument.bodyの位置の差を求め… this.marker.parentNode.removeChild( this.marker ); this.targets[ 0 ].parentNode.appendChild( this.marker ); this.marker.style.left = mkx - dx + "px"; this.marker.style.top = mky - dy + "px"; this.marker.style.width = mkw + "px"; this.marker.style.height = mkh + "px"; //bodyを親とした場合の座標に差分を適用して目的の座標となる。 //check. スクロールしない if( ! doScroll || this.targets.length == 0 ) { return; } //---ターゲットまでスクロールする //check. targetsは画面内か let ron = this.navi.getBoundingClientRect(); //ex. rect of navi let row = { //ex. rect of window left : 0, top : 0, width : this.innerWidth(), height : this.innerHeight() - ron.height * 1.50, } if( this.utl_firstIsInSecond( rot, row ) ) { //すでに画面内にあるならスクロール動作をしない return; } //スクロール位置 let y = scrollY + rot.top; let rect = this.char.getBoundingClientRect(); let viewHeight = this.innerHeight() - rect.height; if( rot.height > viewHeight ) { //targetsのtopがwindow上端に来るように(追加計算なし) } else { y += rot.height / 2; //targetsの中心が y -= viewHeight * .5; //見えるエリアの50%の位置に } //check. 負値を修正 y = y < 0 ? 0 : y; //check. 最下部越えを修正 let body = document.body || document.documentElement; let pageHeight = body.scrollHeight; //ページ全体の長さ if( y > pageHeight - this.innerHeight() ) y = pageHeight - this.innerHeight(); //check. if( ! doAnimate ) { scrollTo( 0, y ); return; } //スクロール速度 let tm; let len = Math.abs( y - scrollY ); if( len < this.innerHeight() ) tm = 1000; else tm = 2000; if( isSync ) { //スクロールを完了するまで次の処理を行わない場合 //(この関数自体の呼び出し時、asyncやawaitを使用のこと) return new Promise( async function( tellOk ) { await this.setScrollAnimation( y, tm ); tellOk(); }.bind( this ) ) } else { //スクロールしつつ次の処理を行う場合 this.setScrollAnimation( y, tm ); } }//calcAndApplyPosition //--method targetting targetting( idstr ) { this.untarget(); let ids = idstr.split( /,/ ); this.targets.length = 0; let st = document.getElementById( ids[ 0 ] ); let et = document.getElementById( ids[ ids.length - 1 ] ); //check. if( ! st || ! et ) { alert( "エラー\n説明対象のプログラムリストがこのページ上にありません。(ID: " + idstr + ")" ); return; } //check. if( st.parentNode != et.parentNode ) { alert( "2つの要素は同じ親に属す必要があります@targetting()" ); return; } let pc = Array.from( st.parentNode.children ); let iost = pc.indexOf( st ); let ioet = pc.indexOf( et ); //ex. index of start target for( let i = iost; i <= ioet; i++ ) { let target = pc[ i ]; this.targets.push( target ); } if( this.place ) this.placeBak = this.place; this.place = this.targets.map( t => t.id ).join( "," ); this.placeSeek = this.seek2; //targetのstyle変更 let newStyleOfTarget = { //ex. new style of target position : "relative", zIndex : 10, } let newStyle = { // color : "red", // textShadow : "-1px 0px 0px white, 0px 1px 0px gray", textShadow : "1px 0px 0 rgba(0,0,0,.5) inset, -1px 0px 0px rgba(0,0,0,.5) inset", } //各行の座標の親を探す let p = this.targets[ 0 ]; let pcs; let limit = 100; do { p = p.parentNode; pcs = getComputedStyle( p ); } while( limit-- && [ "absolute", "relative" ].indexOf( pcs.position ) == -1 ); this.testObjects = Array(); for( let i = 0; i < this.targets.length; i++ ) { let target = this.targets[ i ]; let computedStyleOfTarget = getComputedStyle( target ); //スタイルをバックアップし、新しいスタイルを適用 this.targetsStyleBak[ i ] = new Object(); for( let prop in newStyleOfTarget ) { this.targetsStyleBak[ i ][ prop ] = computedStyleOfTarget[ prop ]; target.style[ prop ] = newStyleOfTarget[ prop ]; } //targetの内部span(文字)のstyle変更 let cStyle = getComputedStyle( target ); this.targetsStyleBak2[ i ] = new Object(); for( let prop in newStyle ) { this.targetsStyleBak2[ i ][ prop ] = cStyle[ prop ]; //backup target.style[ prop ] = newStyle[ prop ]; } //step1. backup let recursive = function( target ) { //check. SPANかDIVに限る if( target.tagName != "SPAN" && target.tagName != "DIV" ) return; let cs = getComputedStyle( target ); let testObject = new Object(); this.testObjects.push( testObject ); testObject.element = target; testObject.color = cs.color; for( let child of target.children ) { recursive( child ); } }.bind( this ); recursive( target ); //step2. change let proportion = 0.4; //proportion = 比率の意 for( let obj of this.testObjects ) { let matchies; if( matchies = obj.color.match( /rgb\((\d+), (\d+), (\d+)\)/ ) ) { let R, G, B; R = Number( matchies[ 1 ] ) * proportion; G = Number( matchies[ 2 ] ) * proportion; B = Number( matchies[ 3 ] ) * proportion; obj.element.style.color = "rgb(" + R + "," + G + "," + B + ")"; } else if( matchies = obj.color.match( /rgba\((\d+), (\d+), (\d+), ([\d.]+)\)/ ) ) { let R, G, B, A; R = Number( matchies[ 1 ] ) * proportion; G = Number( matchies[ 2 ] ) * proportion; B = Number( matchies[ 3 ] ) * proportion; A = Number( matchies[ 4 ] ); obj.element.style.color = "rgba(" + R + "," + G + "," + B + "," + A + ")"; } } //疑似要素(行番号)のstyle変更 let prect = p.getBoundingClientRect(); let trect = target.getBoundingClientRect(); let diff = trect.left - prect.left - delpx( pcs.borderLeftWidth ); target.style.setProperty( "--leftValue", -diff + "px" ); }//for targets this.pointer.style.display = 'block'; this.marker.style.display = "block"; this.calcAndApplyPosition( true, true ); }//targetting() utl_trim( txt ) { return txt.replace( /(^[\s\n]+|[\s\n]+$)/g, "" ); } //--method utl_firstIsInSecond utl_firstIsInSecond( f, s ) { return f.left >= s.left && f.left + f.width <= s.left + s.width && f.top >= s.top && f.top + f.height <= s.top + s.height; } //--method utl_getElementsRect utl_getElementsRect( elements ) { //check. 要素が1つ if( elements.length == 1 ) return elements[ 0 ].getBoundingClientRect(); let res = { left : Infinity, top : Infinity, right : -Infinity, bottom : -Infinity, } for( let element of elements ) { let roe = element.getBoundingClientRect(); res.left = Math.min( res.left, roe.left ); res.top = Math.min( res.top, roe.top ); res.right = Math.max( res.right, roe.right ); res.bottom = Math.max( res.bottom, roe.bottom ); } res.width = res.right - res.left; res.height = res.bottom - res.top; return res; } //--method setScrollAnimation setScrollAnimation( y, tm ) { return new Promise( function( tellOk ) { //check. if( typeof this.timerId !== "undefined" ) { clearTimeout( this.timerId ); delete this.timerId; } let anm = new Object(); anm.init = function( timerMs ) { this.name = "scroll"; this.value = window.scrollY; this.endValue = y; this.sign = this.value <= this.endValue ? 1 : -1; this.endFlg = false; this.tellOk = tellOk; this.graph = { xs : -Math.PI * 0.5, xe : Math.PI * 1.5, NoT : tm / timerMs, //ex. number of times = 回数 } this.graph.f = function( cnt ) { let x = this.xs + ( this.xe - this.xs ) / this.NoT * cnt; return ( Math.sin( x ) + 1.1 ) / 2.1; }.bind( this.graph ); this.graph.zm = 0; for( let c = 0; c <= this.graph.NoT; c++ ) { this.graph.zm += this.graph.f( c ); } this.graph.zm = ( this.endValue - this.value ) / this.graph.zm; //debug. if( 0 ) for( let c = 0; c <= this.graph.NoT; c ++ ) { console.log( c, this.graph.next() ); } this.cnt = 0; }.bind( anm ); anm.frame = function() { let add = this.graph.f( this.cnt++ ) * this.graph.zm; this.value += add; //check. 終了 if( Math.abs( this.value - this.endValue ) < 1 ) { this.value = this.endValue; this.endFlg = true; } window.scrollTo( 0, this.value ); return this.endFlg; }.bind( anm ); anm.init( this.timerMs ); let frame; frame = function() { anm.frame(); //check. if( anm.endFlg ) { anm.tellOk(); // this.calcAndApplyPosition(); //画面に現れたタイミングで //サイズが変わる要素に対応するため //スクロール終了で座標やサイズを再計算する return; } this.timerId = setTimeout( frame.bind( this ), this.timerMs ); }.bind( this ) frame(); }.bind( this )//Promise );//Promise }//setScrollAnimation() //--method untarget untarget() { //check. if( this.targets.length == 0 ) return; for( let object of this.testObjects ) { object.element.style.color = object.color; } //targetのstyle復元 for( let i = 0; i < this.targets.length; i++ ) { let target = this.targets[ i ]; for( let prop in this.targetsStyleBak[ i ] ) { target.style[ prop ] = this.targetsStyleBak[ i ][ prop ]; } //テキスト部分のstyle復元 for( let prop in this.targetsStyleBak2[ i ] ) { target.style[ prop ] = this.targetsStyleBak2[ i ][ prop ]; } //疑似要素(行番号)のstyle復元 target.style.setProperty( "--leftValue", "0" ); } this.pointer.style.display = "none"; this.marker.style.display = "none"; this.targetsBak = Array.of( ...this.targets ); this.targets.length = 0; } //--method play async play() { do { let navi = this.data.navis[ this.seek ]; this.huki.innerHTML = ""; if( navi.script ) { let lines = navi.script.toString().match( /\/\*([\s\S]*)\*\// )[ 1 ].split( /\n/ ); while( ++this.seek2 < lines.length ) { let line = lines[ this.seek2 ]; // line = line.replace( /\s*\/\/.*$/, "" ); line = line.replace( /^\s+|\s+$/g, "" ); //check. if( line == "" ) continue; if( line.match( /:([a-z0-9_$@,\s]+)$/i ) ) { let idstr = RegExp.$1; //idをカンマで複数指定の場合あり this.targetting( idstr ); this.huki.innerHTML = ""; } else if( line.match( /.+;$/ ) ) { //末尾がセミコロンの場合はJavaScript実行 eval( line ); } else { //それら以外は、メッセージ await this.mess( line ); await this.hitspacekey(1); } } this.seekAtEndOfPart = this.seek2 - 2; } this.seek++; this.seek2 = -1; } while( this.seek < this.data.navis.length ); this.destroy(); } hitspacekey( num ) { return new Promise( function( tellOk ) { this.hitspacekeyDiv.style.display = "inline-block"; onkeydown = function( e ) { if( e.which == 32 ) { tellOk(); return false; } return true; } this.navi.onclick = function( e ) { tellOk(); return true; } }.bind( this ) ).then( function() { onkeydown = null; this.navi.onclick = null; this.hitspacekeyDiv.style.display = "none"; }.bind( this ) ); }//hitspacekey //--method mess mess( messageHTML ) { return new Promise( function( tellOk ) { let before = this.huki.innerHTML; let onkeydownBak = window.onkeydown; let onclickBak = this.navi.onclick; //各入力に共通の処理 let endfunc = function() { clearInterval( timerId ); window.onkeydown = onkeydownBak; this.navi.onclick = onclickBak; this.huki.innerHTML += "
"; tellOk(); }.bind( this ); //キー入力 window.onkeydown = function( e ) { if( e.which == 32 ) { this.huki.innerHTML = before + messageHTML; endfunc(); return false; } return true; }.bind( this ); //クリック(タップ) this.navi.onclick = function( e ) { this.huki.innerHTML = before + messageHTML; endfunc(); }.bind( this ); //messageHTMLをタグとテキストに分別 //文字列:例 "テキスト1 タグ1 テキスト2 タグ2 テキスト3" //タグ配列:例 [タグ1,タグ2] let tokens = new Array(); //テキスト配列:例 [テキスト1,テキスト2,テキスト3] let txts = new Array(); //タグとテキストに分別 let tmp = messageHTML; while( tmp.match( /<.+?>/ ) ) { //タグにマッチ let txt = RegExp.leftContext; //マッチの左 let tag = RegExp.lastMatch; //マッチ tmp = RegExp.rightContext; //マッチの右(次処理) txt = this.utl_trim( txt ); txts.push( txt ); tokens.push( "", tag ); //""はのちにテキストが1文字ずつ入る予定 } tmp = this.utl_trim( tmp ); txts.push( tmp ); tokens.push( "" ); //ここで分別完了 //tokensに1文字ずつ文字を入れていく。 let txt = ""; let tokenSeek = -2; let timerId = setInterval( function() { if( txt.match( /^./ ) ) { //先頭の1文字にマッチ //1文字追加処理 txt = RegExp.rightContext; //マッチの右側 let letter = RegExp.lastMatch; tokens[ tokenSeek ] += letter; //1文字を //現状でtokensを全てつなげて出力 this.kuchipaku( this.getBoin( letter ) ); this.huki.innerHTML = before + tokens.join( "" ); } else if( txts.length > 0 ) { //次のテキストあり //次のテキストへ(一番最初はまずここにくる) txt = txts.shift(); tokenSeek += 2; } else { //次のテキスト無し //テキストがすべて終了したので全終了 // console.log( "[" + tokens.join( "][" ) + "]" ); endfunc(); } }.bind( this ), 100 ); }.bind( this )//function );//new Promise }//mess() getBoin( letter ) { let lut = { "あかさたなはまやらわ" : "a", "いきしちにひみいりい" : "i", "うくすつぬふむゆるう" : "u", "えけせてねへめえれえ" : "e", "おこそとのほもよろを" : "o", "ん" : "n", "~ー・" : this.beforeBoin, } let keys = Object.keys( lut ); for( let key of Object.keys( lut ) ) { if( key.indexOf( letter ) > -1 ) { this.beforeBoin = lut[ key ]; return lut[ key ]; } } let values = Object.values( lut ); return values[ Math.floor( Math.random() * values.length ) ]; } kuchipaku( boin ) { //console.log( "image: " + boin ) } hideChar() { this.navi.style.display = "none"; } showChar() { this.navi.style.display = "block"; } }