質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.37%

inputの「範囲を超えた分を左右キーで表示」を、divに実装する方法

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 891

score 5

前提・実現したいこと

inputのような「範囲を超えた分を左右キーで表示」を、divに実装したいです。

たとえば次のコードならば、範囲を超えた「さしすせそ」あたりも左右キーで表示させることができますよね?

<input type="text" value="あいうえおかきくけこさしすせそ">

このような機能をdivに対しても実装したいと考えております。

尚、divの中身はtextareaの入力値をコピーしたものになります。

発生している問題

下記のソースコードですが、表示用のdivの文字が長いとき、見えなくなってしまうのが問題です。

先のinputでいえば「さしすせそ」あたりが見えなくなってしまうということです。

これを解決し、inputのような「範囲を超えた分を左右キーで表示」をdivにも実装したいわけです。

該当のソースコード

  <div class="box">        
    <textarea class="dammy"></textarea>
    <div class="copy"></div>
  </div>
  .box {
      position: relative;
      width: 200px;
      height: 40px;
      margin: 0;
  }
  .dammy,
  .copy {
      border: none;
      padding: 0.75rem 1rem;
      width: 100%;
      height: 40px;
      line-height: 1.5;
      min-height: 40px;
      font-size: 1.5rem;
      font-family: sans-serif;
      white-space: nowrap;
      overflow: hidden;
  }
  .dammy {
      position: absolute;
      z-index: 1;
      caret-color: blue;
      border: none;
      background: transparent;
      color: transparent;
      resize: none;
  }
  .copy {
      height: auto;
      position: relative;
      z-index: 0;
      background: #ddd;
  }
  .copy .within {
      color: blue;
      white-space: nowrap;
      display: inline;
  }
  .copy .overed {
      color: red;
      white-space: nowrap;
      display: inline;
  }
  // 入力
  $( document ).on( 'input', '.dammy', function( e ){
    const $dammy = $( this );
    // 改行禁止
    enter( $dammy, e );
    // コピー
    const lim = 5;
    copy( $dammy, lim );
  });

  // 改行禁止
  function enter( $dammy, e ){
    $dammy.on( 'keydown', function( e ) {
      if ( e.which == 13 ) { return false; }
    });
    const val = $dammy.val();
    const reg = new RegExp( "[\r\n]", "g" );
    if ( val.match(reg) ) {
      const replace = val.replace( reg, '' );
      $dammy.val( replace );
    }        
  }

  // コピー
  function copy( $dammy, lim ){
    const $copy = $dammy.next( '.copy' );
    const val = $dammy.val();
    if ( val.length > lim ) {
      const within = val.substr( 0, lim );
      const overed = val.slice( lim );
      $copy.html('<div class="within">' +within+ '</div>');
      $copy.append('<div class="overed">' +overed+ '</div>');
    }
    else{
      $copy.html('<div><div class="within">' +val+ '</div></div>');
    }
  }    

試したこと

上は「textareaの入力値をdivにコピーする」という方法ですが、他にも「contenteditableをtrueにする」という方法を試しました。これならば「範囲を超えた分を左右キーで表示」が可能です。

ですが、お詳しい方ならご存じかと思いますが、HTMLを加えるとキャレット位置がズレてしまい修正が困難であったり、その他いろいろな点で問題がございましたので見送りました。

補足情報(FW/ツールのバージョンなど)

補足情報は特に現状ではありませんが、質問内容に不足などございましたら仰ってください。
質問は以上になります。ご協力いただけましたら幸甚に存じます。
何卒よろしくお願い致します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • siruku6

    2019/10/06 16:00

    サンプルコードが動作しませんでした。
    <style>の閉じタグがないことは確認できたのですが、他にもありそうです。(入力してもdivに文字がコピーされるという動作が再現できないため)

    実際に動作しているソースを貼り付けておきましょう。

    キャンセル

  • kanimiso

    2019/10/06 16:12

    おっと、失礼いたしました。style閉じ忘れました。ありがとうございます。
    あとは平気だと思います↓
    https://jsfiddle.net/t7sjLh6a/

    キャンセル

  • siruku6

    2019/10/06 16:23

    これで大丈夫そうですね。

    キャンセル

回答 2

checkベストアンサー

0

目的としては、textareaの中身が左右にスクロールした分だけ、divも左右にスクロール出来ればいい話なので。。。

とりあえずそれっぽくしてみました。

CSSは、
divのスクロール時の見た目を、テキストエリアと合わせるために、ちょっと調整します。。

  .box {
      position: relative;
      width: 200px;
      height: 40px;
      margin: 0;
      padding: 0.75rem 1rem;
      line-height: 1.5;
      background: #ddd;
  }
  .dammy,
  .copy {
      border: none;
      padding: 0;
      height: 40px;
      line-height: 1.5;
      min-height: 40px;
      font-size: 1.5rem;
      font-family: sans-serif;
      white-space: nowrap;
      overflow: hidden;
  }
  .dammy {
      width: calc(100% - 2rem);
      position: absolute;
      z-index: 1;
      caret-color: blue;
      border: none;
      background: transparent;
      color: transparent;
      resize: none;
  }
  .copy {
      width: 100%;
      height: auto;
      position: relative;
      z-index: 0;
      background: #ddd;
  }
  .copy .within {
      color: blue;
      white-space: nowrap;
      display: inline;
  }
  .copy .overed {
      color: red;
      white-space: nowrap;
      display: inline;
  }

js は、以下を追加

$(document).on('keyup', '.dammy', function(){
    $(this).next('.copy').scrollLeft($(this).scrollLeft());
});

見ての通りkeyupしないとイベントが発火しないため、
左右キー長押しによって、カーソルが1文字ずつ移動するような動作に【追従するスクロール】は再現出来ません。(長押しは、キーを離すと位置が追いつきます)

もし、左右キー長押しも再現したいなら、keydownも使って「押し続けている間」にもdivのスクロール位置の調整処理を繰り返せるように。。と、もう少し工夫が必要になりますね。

※ windowsのChrome、Firefox、IE11、Edgeで簡単に動作確認はしましたが、ちょっとしか動かしてみてないので、もし不具合等ございましたらご容赦を。。

ご参考までに。。^^


コメント欄で話に出た「選択の動作」について追記します。

現状の入力欄は、textareaで、overflow:hiddenに設定されているため、
そもそもこの入力欄自体が、話にでたような「選択の動作」を行ってくれません。
なので、これを実現するためには、textarea自体に工夫が必要になります。

試しに、
マウスをクリックしてから離すまでの間、textareaの左右のスクロールを自動で調整するようにしてみました。

  var scroller = false, dammy = false, mouse = false;

  $( document ).on('mousedown', '.dammy', function(){
    //.dammyの上でマウスクリックが始まった場合に、自動スクロールの処理を繰り返し動作させます
    dammy = this;
    scroller = setInterval(dammy_scroll, 20);
  }).on('mousemove', function(){
    //画面内で移動したマウスのポジションを使いたいのでここで保持しておきます。
    mouse = event;
  }).on('mouseup', function(){
    //どこかでマウスクリックが終わったら、繰り返し処理を削除します。
    if(scroller){
         scroller = clearInterval(scroller);
    }
    dammy = false;
  });

  function dammy_scroll(){
    if(!dammy){
      return ;
    }
    if($(dammy).offset().left > mouse.clientX){ //入力欄エリアよりも、マウスが左側にいる
       $(dammy).scrollLeft($(dammy).scrollLeft() - 5);
    }else if(($(dammy).offset().left + $(dammy).width()) < mouse.clientX){//入力欄エリアよりも、マウスが右側にいる
        $(dammy).scrollLeft($('.dammy').scrollLeft() + 5);
    }
    //divのスクロール量を、textareaに合わせます。
    $(dammy).next('.copy').scrollLeft($(dammy).scrollLeft());
  }


自動のスクロールを5pxにしたので、通常の「選択の動作」よりも若干ゆっくりですが、
大体似たような感じになった気がします。

ちなみに。

textareaの、overflow:hiddenを削除する(これだと、幅を超える文字数を入力した時に、スクロールバーが見えてしまいますので、そちらを隠す方法を検討する必要がありますね・・)
textareaではなくinputに変更する

のどちらかが可能なのであれば、
textarea/input自体が、話に出た「選択の動作」をしてくれるので、上記のようなコードは不要となり、

var dammy= false;
//画面外でマウスクリックが終わった時も反応したいので、イベント設定対象はdocumentに。
$(document).on('mousedown', '.dammy',function(){
  dammy= this;
}).on('mouseup', function(){
  if(dammy)
    $(dammy).next('.copy').scrollLeft($(dammy).scrollLeft());
  }
  dammy = false;
});


とするだけでも、再現は出来そうでしたよ。

※追記のコードは、windowsのChromeで軽く動作確認しただけのものなので、その点はご了承を。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/08 18:29

    「選択の動作」に関する回答のご追記、誠にありがとうございます。scrollLeftというメソッドがあったんですね。そちらfirefoxでも動作しまして、inputに変更するプランが理想に近いとなりました。どうもありがとうございます。僭越ながらベストアンサーとさせて頂きました。
    選択された青い範囲だけ微妙に文字の高さにずれが生じますが、その辺りはご愛敬(?)ということで勘弁してもらおうと思います。笑

    たびたび誠に恐れ入りますが、最後にもう一つだけ教えて頂けないでしょうか。
    ご追記のコードで、➀dammyにfalseを入れて、➁マウスの対象を入れて、➂ifで判定し、➃最後にfalseに戻していらっしゃいますよね?この一連の流れの意味がわかりませんでした。
    もしお時間と余力がございましたら軽くご解説願えませんでしょうか。

    キャンセル

  • 2019/10/10 15:44

    変数dammyは、mousedownのイベント設定がクラス指定のため、同名クラスの要素が複数ある前提で、スクロールの調整が必要な対象1つだけを保持して置くものです。
    提示したコード上では、「dammy」に何か入っている間だけしか、dammy_scroll() が繰り返し呼ばれることはない、ようになっています。
    が、今回、dammy_scroll()は、globalな関数で定義しているので、この関数自体が、上記コード外からも呼び出しが可能です。
    想定外のところからdammy_scroll()が実行された場合、変数dammyの中身がないとエラーが発生しますし、変数dammyに「前回イベントの発生した.dammy」を入れっぱなしにしておくと、本来意図しないタイミングで、スクロール調整が動くことになってしまいます。(機能上、入れっぱなしの方は大した問題ではありませんが・・)
    この2点を回避するために、mouseup時にわざわざfalseを入れ直したり、dammy_scroll の中で、変数の存在判定を入れたりしました。

    サンプルとしてお出ししたコードなので、このあたりの考慮は不要だったかもしれませんね。
    ただ、簡単に想定できる問題になりうる点は回避できるように、ついこんな流れにしてしまいました。

    キャンセル

  • 2019/10/10 15:48

    いえサンプルとしてお出し頂いたにも関わらず、すばらしいご考慮であったと感激しております。必要な機能でした。そのテクニックも当方では思いつくことはできなかったでしょうし、改めて感謝申し上げます。

    キャンセル

0

css と javascript の部分を以下のように変えれば、それなりに見える動きになりました。
ただし、青色、赤色に文字色を変えるという部分を削除してしまいましたので、そこは修正してみてください。

      .box {
          position: relative;
          width: 200px;
          height: 40px;
          margin: 0;
      }
      .dammy,
      .copy {
          border: none;
          padding: 0.75rem 1rem;
          width: 100%;
          height: 40px;
          line-height: 1.5;
          min-height: 40px;
          font-size: 1.5rem;
          font-family: sans-serif;
          white-space: nowrap;
          overflow: hidden;
      }
      .dammy {
          position: absolute;
          z-index: 1;
          caret-color: blue;
          border: none;
          background: transparent;
          color: transparent;
          resize: none;
      }
      .copy {
          height: auto;
          position: relative;
          z-index: 0;
          background: #ddd;
      }
      .copy .within {
          color: blue;
          white-space: nowrap;
          display: inline;
      }
      .copy .overed {
          color: red;
          white-space: nowrap;
          display: inline;
      }

      /* style-sheet 追加 */
      .right-aligned {
        position: sticky;
        right: 0;
      }
      // 入力
      $( document ).on( 'input', '.dammy', function( e ){
        const $dammy = $( this );
        // 改行禁止
        enter( $dammy, e );
        // コピー
        const lim = 5;
        copy( $dammy, lim );
      });

      function enter( $dammy, e ){
        $dammy.on( 'keydown', function( e ) {
          // 改行禁止
          if ( e.which == 13 ) {
            return false;
          // 横矢印キー押下時の動作
          } else if ( e.which == 39 ) {
            $('.within').addClass('right-aligned');
          } else if ( e.which == 37 ) {
            $('.within').removeClass('right-aligned');
          }
        });
        const val = $dammy.val();
        const reg = new RegExp( "[\r\n]", "g" );
        if ( val.match(reg) ) {
          const replace = val.replace( reg, '' );
          $dammy.val( replace );
        }
      }

      // コピー
      function copy( $dammy, lim ){
        const $copy = $dammy.next( '.copy' );
        const val = $dammy.val();
        // if ( val.length > lim ) {
        const within = val //.substr( 0, lim );
        // const overed = val.slice( lim );
        $copy.html('<div class="within">' +within+ '</div>');
        // $copy.append('<div class="overed">' +overed+ '</div>');
        // } else {
        //   $copy.html('<div><div class="within">' +val+ '</div></div>');
        // }
      }    

追記

次のような一つのhtmlファイルの状態にして、ファイルをダブルクリックしたところ、動作しました

残念なところとしては、隠れた部分を表示したとき、カーソルの位置と文字の位置が合っていないところです。
あくまで、できたのは「表示するだけ」になってしまっております....

<!DOCTYPE html>
<html lang="ja">

    <head>
      <title>Team1 Website</title>
      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

      <style>
      .box {
          position: relative;
          width: 200px;
          height: 40px;
          margin: 0;
      }
      .dammy,
      .copy {
          border: none;
          padding: 0.75rem 1rem;
          width: 100%;
          height: 40px;
          line-height: 1.5;
          min-height: 40px;
          font-size: 1.5rem;
          font-family: sans-serif;
          white-space: nowrap;
          overflow: hidden;
      }
      .dammy {
          position: absolute;
          z-index: 1;
          caret-color: blue;
          border: none;
          background: transparent;
          color: transparent;
          resize: none;
      }
      .copy {
          height: auto;
          position: relative;
          z-index: 0;
          background: #ddd;
      }
      .copy .within {
          color: blue;
          white-space: nowrap;
          display: inline;
      }
      .copy .overed {
          color: red;
          white-space: nowrap;
          display: inline;
      }

      /* style-sheet 追加 */
      .right-aligned {
        position: sticky;
        right: 0;
      }
      </style>

      <script>
      // 入力
      $( document ).on( 'input', '.dammy', function( e ){
        const $dammy = $( this );
        // 改行禁止
        enter( $dammy, e );
        // コピー
        const lim = 5;
        copy( $dammy, lim );
      });

      function enter( $dammy, e ){
        $dammy.on( 'keydown', function( e ) {
          // 改行禁止
          if ( e.which == 13 ) {
            return false;
          // 横矢印キー押下時の動作
          } else if ( e.which == 39 ) {
            $('.within').addClass('right-aligned');
          } else if ( e.which == 37 ) {
            $('.within').removeClass('right-aligned');
          }
        });
        const val = $dammy.val();
        const reg = new RegExp( "[\r\n]", "g" );
        if ( val.match(reg) ) {
          const replace = val.replace( reg, '' );
          $dammy.val( replace );
        }
      }

      // コピー
      function copy( $dammy, lim ){
        const $copy = $dammy.next( '.copy' );
        const val = $dammy.val();
        // if ( val.length > lim ) {
        const within = val //.substr( 0, lim );
        // const overed = val.slice( lim );
        $copy.html('<div class="within">' +within+ '</div>');
        // $copy.append('<div class="overed">' +overed+ '</div>');
        // } else {
        //   $copy.html('<div><div class="within">' +val+ '</div></div>');
        // }
      }    
      </script>
    </head>

  <body>
    <div class="box">
      <textarea class="dammy"></textarea>
      <div class="copy"></div>
    </div>
  </body>

</html>

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/06 18:41

    いえ、それができれば問題ないです。どのような環境でできましたか?こちらで一通り実行しましたができなかったので、何が解決したのか掴めないと申し上げた次第です。

    キャンセル

  • 2019/10/06 18:50 編集

    1つのhtmlにすべてまとめ、windows上でそのファイルをダブルクリックしたところ、動作させることができました。

    回答欄に追記しています。
    残念ながら、使いにくい動作をしている部分があります...

    キャンセル

  • 2019/10/06 19:26 編集

    やはり動作していないと思われます。こちら画像をご覧ください。
    https://imgur.com/a/hTbKuhJ
    「あいうえおかきくけ」までしか表示できず、左右キーで「こさしすせそ」などが表示されることはありませんでした。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る