画像の「ウインドウサイズ」「等倍表示」の切換え

「元データ画像」の「#box-img」がロードされた最初は、通常は「#lightbox」より表示サイズが大きいのですが、この画像に以下の指定をしてボックス内に収まる様にしています。

 

「width: 98vw; height: 98vh; padding: 1vh 1vw; object-fit: contain;」

 

 

これを「等倍表示」に変更するには、画像に以下の指定をします。

 

「width: auto; height: auto; padding: 0;」

 

この指定下で「object-fit: contain;」は無効化するので残したままですが、下の様に「#box-img」は「#lightbox」よりサイズが大きくなります。

 

 

しかし、画像の左上隅以外の部分は表示できないので、「#lightbox」に対してはみ出した部分を表示できる様に「overflow: auto;」の指定をします。

 

考えると、最初から「#lightbox」にこの指定があれば、画像のサイズ変化に応じて、自動的にスクロールバーの「表示・非表示」が決まります。 いちいちスクリプトで指定を変える必要がないので、この指定をデフォルトで指定しました。

 

 

下は、この「ウインドウサイズ」と「等倍表示」を切り換えるコードです。

 

photo_link.onclick=function(event){
    event.stopImmediatePropagation();
    if(box_img.style.width!='auto'){
        let org='width: auto; height: auto; padding: 0;';
        box_img.setAttribute('style', org);
        photo_link.textContent='🔲 Fit on Screen'; }
    else{
        get_pos(lightbox);
        let nor='width: 98vw; height: 98vh; padding: 1vh 1vw;';
        box_img.setAttribute('style', nor);
        photo_link.textContent='⬜ View Actual Pixels'; }}

 

ここまでが前ページの「ver. 0.4」のコードです。

 

 

 

 「等倍表示」で 最初は中央を表示する

「Lightbox」内の2種の表示を切替え可能にして、すぐに気付いたのは「等倍表示」が「左上隅」から表示される点です。 最初に画像の隅を拡大表示するのは、いかにも不適当です。 中央に主題がある写真ばかりではありませんが、中央からスクロール移動すれば総じて楽でしょう。

 

そこで、最初に「等倍表示」に入った時は、中央を表示する様にしました。 これには、スクロール量の計算が必要でした。

 

 

スクロール量の計算 

ちょっと図を書いてみると、スクロールの計算方法が見えて来ます。

 

 

上図は、横方向のスクロール量の計算です。 最初は左端を表示していたボックスを、画像「#box-img」の幅の1/2だけ左端からスクロールすると、これは行き過ぎになります。「#lightbox」自身の幅の1/2だけ戻してやれば、画像の中央がボックスの中央に重なるはずです。

 

ここから、横方向のスクロール量を計算するコードは以下となります。

 

axtal_hm=(disp_img.clientWidth - box.clientWidth)/2

 

axtal_hm:必要な横スクロール量

disp_img.clientWidth:画像の表示サイズ

box.clientWidth:表示ボックスのサイズ

 

縦方向のスクロール量も、同様に計算できます。 この計算方法を使い、画像を表示ボックスの中央にスクロールする関数を作りました。 関数の引数「box」は表示ボックスの要素、「disp_img」は画像の要素です。

 

function mag_pos(box, disp_img){
        let axtal_vm=(disp_img.clientHeight - box.clientHeight)/2
        let axtal_hm=(disp_img.clientWidth - box.clientWidth)/2
        box.scrollTo(axtal_hm, axtal_vm); }

 

 

「等倍表示」の最初だけに実行 

この関数を、「等倍表示」に切り替えた際に割り込ませれば、「等倍表示」が常に中央から始まりますが、これもまた不便です。 画像の検証中に「ウインドウサイズ」に戻したり、あるいは「Lightbox表示」を抜けて元の「ユーザー別写真リスト」に戻る事も考えられます。 この3つの表示状態で、「等倍表示」が中央になって欲しいのは最初だけで、その後は最後に表示していた場所を表示するのが、一番便利そうです。

 

このために、「等倍表示」時の「#lightbox」のスクロール位置を記録する事が必要でした。 また、「ユーザー別写真リスト」の画面を開いてから「等倍表示」に入った場合をカウントして、最初の場合のみに中央へスクロールを実行する様にしました。

 

これは、フラグのグローバル変数を用意し、処理の適切な場所で変数に値を代入し、各操作の実行時にその値を参照して条件分岐すれば、比較的簡単に実現できました。「ver. 0.5」で用意した変数は以下です。

 

let first_vew=0; // Actual Pixels表示履歴のフラグ
let actal_v; // Actual Pixels表示履歴スクロールx値
let actal_h; // Actual Pixels表示履歴スクロールy値

 

 

「元データ画像」が小型だった場合 

たまにあるのですが、PHOTOHITOにアップロードされた画像サイズがとても小さい場合があります。 その場合は「ウインドウサイズ」の表示より「等倍表示」の方が小さく表示されます。 これまでは、「Lightbox」に「text-align: center」を指定して中央上部に黒背景に表示していましたが、これも中央配置にしよう考えました。

 

#lightbox { display: grid; place-items: center; }

 

上記がネットを探して得た最適解です。 最初「display: flex;」でなんとかしようとしたのですが、中央配置のコードが上手く動作しなくなりました。 似た様なCSS機能ですが「display: grid;」の中央配置は、機序が異なって、中央配置のコードがそのまま使えました。 私は使い慣れない「grid」ですが、大事な事として記しておきます。

 

 

 

「等倍表示」の表示仕様の更新 

「ユーザー別写真リスト」で画像を「左Click」すると「Lightbox」を表示します。

最初に表示されるのは、「元データ画像」の「ウインドウサイズ」表示です。

 

●「ウインドウサイズ」の画面上部をポイントすると「⬜ View Actual Pixels」のボタンが表示されます。

 

●「⬜ View Actual Pixels」を「左Click」すると「等倍表示」になります。

▪ 最初の「等倍表示」は、画像の中央を表示します。

▪ 現在開いている画像の2回目以降の「等倍表示」は、その前に「等倍表示」で表示していた場所を表示します。

▪ いったん「ウインドウサイズ」の表示に戻った場合も、再度「等倍表示」にすると、その前の「等倍表示」のスクロール位置を再現します。

▪ いったん「ユーザー別写真リスト」の表示に戻っても、再度「Lightbox」を表示した場合は、その前の「Lightbox」の表示を再現します。

▪「ユーザー別写真リスト」を閉じて、別の「ユーザー別写真リスト」を開いた場合は、「等倍表示」のスクロール位置はリセットされ初回の中央表示になります。

 

●「等倍表示」の画面上部をポイントすると「🔲 Fit on Screen」のボタンが表示されます。

 

●「🔲 Fit on Screen」を「左Click」すると「ウインドウサイズ」になります。

 

●「ウインドウサイズ」「等倍表示」のどちらの場合も「Lightbox」内のスイッチ以外の部分を「左Click」すると、「ユーザー別写真リスト」の画面に戻ります。

 

 

 

 ひとつの完成型

このページの「ver. 0.5」は、一定の完成型だと思います。 通常は、そう不足を感じないし、大きな問題点はなさそうです。 ただ現在、ちょっと欲が出て来ました。 

 

「Lightbox表示」の「ウインドウサイズ」の表示で、マウスでポイントした位置を中心に「等倍表示」できないかという案です。「PhotoShop」ではそういう操作が可能で便利なのですが、その様なコードを作った事がないので、挑戦したくもあります。

 

ただ、このコードを採り入れるには、「Lightbox」内の操作仕様を、広範囲に見直す必要があります。 スイッチで「ウインドウサイズ」⇄「等倍表示」を変更する方式が不要になり、初回の中央表示も不要になります。 一方、現在は「Lightbox」内をクリックすれば通常表示に戻りますが、これを変更するのかという問題など、操作の覚え易さ、使い勝手の良さ、スイッチや表示類のサポートなどを含めて、よく考える必要があるので。

 

 

〔追記〕 2022.11.10

検証が甘かったです。 PHOTOHITOに画像をアップロードするシステムは、画像の検証場所と同じ「photo」の「path」で、ツールの適用するスタイルが誤適用されていました。 このページの「ver. 0.5」は、アップロード後に、再度この適用先の修正をしています。

 

 

 

「PhotoHito Viewer」の扱い方 

基本的な扱い方は、下のページを参照ください。 ただし、バージョンごとに仕様を変更しているので、説明が一致しない点はツールの制作記事を参照ください。

 

 

 

 

「PhotoHito Viewer」を利用するには

このツールは Chrome / Edge / Firefox版の拡張機能「Tampermonkey」上で動作します。 以下に、このツールの導入手順を簡単に説明します。

 

❶「Tampermonkey」を導入します

◎ 使用しているブラウザに拡張機能「Tampermonkey」を導入する事が必要です。

既に「Tampermonkey」を導入している場合は、この手順 ❶ は不要です。 

拡張機能の導入については、以下のページに簡単な説明があるので参照ください。

 

 

❷「Tampermonkey」にスクリプトを登録します

◎「Tampermonkey」の「」マークの「新規スクリプト」タブを開きます。

 

 

 

◎「新規スクリプト」には、最初からテンプレートが記入されています。 これは全て削除して、完全に空白の編集枠に 下のコードをコピー&ペーストします。

 

〔コピー方法〕 軽量シンプルなツール「PreBox Button   」を使うと

  コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」

  の操作で、掲載コードのコピーが可能になります。

 

◎ 最後に「ファイル」メニューの「保存」を押すと、ツールが使用可能になります。

 

 

〔 PhotoHito Viewer 〕 ver. 0.5

 

// ==UserScript==
// @name         PhotoHito Viewer
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  写真投稿サイトPHOTOHITO専用の画像検証ビューア
// @author       PHOTOHITO User
// @match        https://photohito.com/photo/*
// @match        https://photohito.com/lens/*
// @match        https://photohito.com/camera/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=photohito.com
// @run-at       document-start
// @grant        none
// ==/UserScript==



let path=location.pathname;

if(path.match(/\/photo\/[0-9]{8}\/$/) || path.startsWith('/lens') || path.startsWith('/camera')){

    let retry=0;
    let interval=setInterval(wait_target, 10);
    function wait_target(){
        retry++;
        if(retry>10){ // リトライ制限 100回 0.1secまで
            clearInterval(interval); }
        let target=document.body; // 監視 target
        if(target){
            clearInterval(interval);
            list_env(); }}


    function list_env(){
        let style=
            '<style id="ph_v">'+
            'h1#logo { display: none; } '+
            'body .wrapper_full article #path { margin: 2px 15px 15px !important; } '+
            '#user_header { margin: -14px 15px 0; } '+
            'body .wrapper article h1 { font-size: 120%; padding: 0 0 12px 40px; cursor: pointer; } '+
            'body .wrapper article h1 i { margin-top: -18px; } '+
            'body .wrapper_full article section { margin-right: 15px !important; } '+
            'body .wrapper_full aside { position: absolute; top: 0; right: 0; padding: 30px 20px; '+
            'background: #fff; z-index: 10; display: none; } '+
            'body .wrapper_full aside .notlogin_banner { display: none; } '+
            'section { display: flex; flex-direction: column; } '+
            '.wrapper article h1 { order: -2; } '+
            '#photo_list_search_form { width: 600px; order: -2; } '+
            '#photo_list_sort {width: 320px; position: relative; top: -50px; left: 600px; order: -2; } '+
            '.photo_list { order: 0; } '+
            '#pagenate { padding: 7px 0; order: -1; margin: -50px 0 20px; background: #f8f8fa; } '+
            '#photo_detail #photo_view { overflow: hidden; } '+
            '#photo_detail #photo_view a { margin: 0 auto !important; width: fit-content; } '+
            '#photo_detail #photo_view a:hover { box-shadow: 0 100vw 0 100vw #000; } '+
            '.adBox-300-2, .ad_detail, .content_ad, '+
            '#div-gpt-ad-p-300x250, #div-gpt-ad-p-side_300x250 { display: none; }'+
            '</style>';

        if(!document.querySelector('#ph_v')){
            document.documentElement.insertAdjacentHTML('beforeend', style); }}

} // 共通CSS




window.addEventListener('DOMContentLoaded', function(){

    let path=location.pathname;
    if(path.match(/\/photo\/[0-9]{8}\/$/)){ //「ユーザー別写真リスト」の画面
        let first_vew=0; // Actual Pixels表示履歴のフラグ
        let actal_v; // Actual Pixels表示履歴スクロールx値
        let actal_h; // Actual Pixels表示履歴スクロールy値



        box_env();
        if(get_cookie('PHView_e')==1){
            exif_env(); }



        let photo_side=document.querySelector('#photo_view');
        if(photo_side){
            photo_side.onclick=function(){ // 画像両サイドのクリックでウインドウを閉じる
                window.close(); }}



        function box_env(){
            let lightbox=
                '<div id="lightbox">'+
                '<div id="photo_sw"><a id="photo_link">⬜ View Actual Pixels</a></div>'+
                '<img id="box_img">'+
                '<style>'+
                '@keyframes fadeIn { 0% {opacity: 0} 100% {opacity: 1}} '+
                '.fin { animation: fadeIn .5s ease 0s 1 normal; animation-fill-mode: both; } '+
                '@keyframes fadeOut { 0% {opacity: 1} 100% {opacity: 0}} '+
                '.fout { animation: fadeOut .2s ease 0s 1 normal; animation-fill-mode: both; } '+
                '#lightbox { position: fixed; top: 0; left: 0; z-index: 3000; '+
                'display: grid; place-items: center; '+
                'background: #000; width: 100vw; height: 100vh; overflow: auto; visibility: hidden; } '+
                '#photo_sw { position: fixed; top: 0; width: 100%; height: 15%; } '+
                '#photo_sw:hover #photo_link { opacity: 1; } '+
                '#photo_link { position: absolute; top: 20px; left: 30px; opacity: 0; '+
                'font: bold 21px Meiryo; padding: 4px 12px 2px 10px; background: #fff; color: #000; '+
                'border: 2px solid #000; border-radius: 6px; cursor: pointer; text-decoration: none; } '+
                '#box_img { width: 98vw; height: 98vh; padding: 1vh 1vw; object-fit: contain; } '+
                '</style></div>'+
                '<div id="exif_sw">'+
                'Exifを上部表示'+
                '<style>'+
                '#exif_sw { position: absolute; top: 84px; left: 500px; z-index: 1; font-size: 15px; '+
                'padding: 11px 10px 8px; border: solid 1px #999; border-radius: 2px; '+
                'cursor: pointer; z-index: 1; }'+
                '</style></div>';

            if(!document.querySelector('#lightbox')){
                document.body.insertAdjacentHTML('beforeend', lightbox); }}



        function exif_env(){
            let ex_style=
                '<style id="exif_style">'+
                '#user_header { z-index: 1; } '+
                '#photo_detail .wrapper:nth-child(3) { width: 1000px !important; } '+
                '#photo_detail #photo_view a { margin: 0 260px 0 0 !important; width: fit-content; } '+
                '#photo_detail #photo_view { display: flex; flex-wrap: wrap; justify-content: center; } '+
                '#photo_detail #photo_view h1 { width: 100%; } '+
                '#sideInfo { position: absolute;top: 90px; left: calc(50vw + 220px); '+
                'padding: 20px 8px; width: 250px; background: #fff; } '+
                '.notlogin_banner, #tag_area, #location_area, #btn_violation { display: none; } '+
                '#photo_view_side { display: none; } '+
                'aside table { margin: 5px 0 0; } '+
                'aside tr { display: flex; flex-direction: column; } '+
                'aside th { padding: 0; font-size: 12px; } '+
                'aside td { padding: 0 0 5px; text-align: left; }'+
                '</style>';

            if(!document.querySelector('#exif_style')){
                document.documentElement.insertAdjacentHTML('beforeend', ex_style); }}



        let exif_sw=document.querySelector('#exif_sw'); // Exifパネルの配置スイッチ
        if(exif_sw){
            exif_sw.onclick=function(){
                let exif_style=document.querySelector('#exif_style');
                if(exif_style){
                    document.cookie='PHView_e=0; path=/; Max-Age=2592000'; // 30日有効
                    exif_style.remove(); }
                else{
                    document.cookie='PHView_e=1; path=/; Max-Age=2592000'; // 30日有効
                    exif_env(); }}}



        let img_link=document.querySelector('#photo_view a');
        let l_img=document.querySelector('#photo_view img:last-child');
        if(img_link && l_img){
            img_link.addEventListener('mousedown', function(event){
                if(!event.shiftKey && !event.ctrlKey){
                    event.preventDefault();
                    set_img(l_img);
                    org_size();
                    close(); }}); }



        function set_img(target){
            let html_=document.querySelector('html');
            let lightbox=document.querySelector('#lightbox');
            let photo_link=document.querySelector('#photo_link');
            let box_img=lightbox.querySelector('#box_img');
            if(lightbox && photo_link && box_img){
                let img_url=target.getAttribute('src');
                if(img_url){
                    let last=img_url.slice(-5);
                    if(last=='l.jpg'){
                        let new_url=img_url.slice(0, -5) +'o.jpg';
                        box_img.src=new_url;
                        html_.style.overflow='hidden';
                        lightbox.style.visibility='visible';
                        lightbox.classList.remove('fout');
                        lightbox.classList.add('fin');
                    }}}}



        function org_size(){
            let lightbox=document.querySelector('#lightbox');
            let photo_link=document.querySelector('#photo_link');
            let box_img=lightbox.querySelector('#box_img');

            photo_link.onclick=function(event){
                event.stopImmediatePropagation();
                if(box_img.style.width!='auto'){
                    let org='width: auto; height: auto; padding: 0;';
                    box_img.setAttribute('style', org);
                    mag_pos(lightbox, box_img);
                    first_vew+=1;
                    photo_link.textContent='🔲 Fit on Screen'; }
                else{
                    get_pos(lightbox);
                    let nor='width: 98vw; height: 98vh; padding: 1vh 1vw;';
                    box_img.setAttribute('style', nor);
                    photo_link.textContent='⬜ View Actual Pixels';
                }}}



        function mag_pos(box, disp_img){
            if(first_vew==0){
                let axtal_vm=(disp_img.clientHeight - box.clientHeight)/2
                let axtal_hm=(disp_img.clientWidth - box.clientWidth)/2
                box.scrollTo(axtal_hm, axtal_vm); }
            else{
                box.scrollTo(actal_h, actal_v); }}


        function get_pos(box){
            actal_v=box.scrollTop;
            actal_h=box.scrollLeft; }



        function close(){
            let html_=document.querySelector('html');
            let lightbox=document.querySelector('#lightbox');
            let box_img=lightbox.querySelector('#box_img');
            if(lightbox){
                box_img.onclick=function(event){
                    event.preventDefault();
                    html_.style.overflow='inherit';
                    lightbox.classList.remove('fin');
                    lightbox.classList.add('fout');
                    setTimeout(()=>{
                        lightbox.style.visibility='hidden';
                    }, 200); }}}



        function get_cookie(name){
            let cookie_req=document.cookie.split('; ').find(row=>row.startsWith(name));
            if(cookie_req){
                if(cookie_req.split('=')[1]==null){
                    return 0; }
                else{
                    return cookie_req.split('=')[1]; }}
            if(!cookie_req){
                return 0; }}


    } //「ユーザー別写真リスト」の画面




    if(path.startsWith('/lens') || path.startsWith('/camera')){ //「サムネイルリスト」の画面

        let plist_a=document.querySelectorAll('.photo_list .grid > a');
        for(let k=0; k<plist_a.length; k++){
            plist_a[k].setAttribute('target', '_blank'); }

        let title=document.querySelector('.wrapper article h1');
        let aside=document.querySelector('.wrapper_full aside');
        if(title && aside){
            title.onclick=function(){
                if(aside.style.display!='block'){
                    aside.style.display='block'; }
                else{
                    aside.style.display='none'; }}}

    } //「lens」「camera」の検索抽出画像の「サムネイルリスト」の画面

}); // main()



 

 

 

「PhotoHito Viewer」最新版について 

旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。

 

●「PhotoHito Viewer」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。