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

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

ただいまの
回答率

87.37%

FirefoxとjQueryで画像を含む要素を複製した時に高さが正しく取得できない

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 482

score 17

恐れ入りますが、ご教授いただければ助かります。
imgタグを複数含む要素をjQueryベースで複製するコードを実行したところ、Firefoxでは高さが正しく取得できないことがあります。以下のようなHTMLで500×500pixelの画像を10個含むul要素を複製しています。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <style type="text/css">
    * {margin: 0; padding: 0;}
    ul {list-style: none;}
    button {padding: 10px;}
  </style>
</head>
<body>
  <button class="copy">複製</button>
  <ul class="images">
    <li><img src="images/test/01.jpg"></li>
    <li><img src="images/test/02.jpg"></li>
    <li><img src="images/test/03.jpg"></li>
    <li><img src="images/test/04.jpg"></li>
    <li><img src="images/test/05.jpg"></li>
    <li><img src="images/test/06.jpg"></li>
    <li><img src="images/test/07.jpg"></li>
    <li><img src="images/test/08.jpg"></li>
    <li><img src="images/test/09.jpg"></li>
    <li><img src="images/test/10.jpg"></li>
  </ul>
  <!-- 以下のjavascriptが続きます -->
  <script type="text/javascript" src="js/jquery-1.9.1.js"></script>
  <script type="text/javascript">
  $(document).ready(function() {
    $('button.copy').click(function() {
      var ul_src = $('ul.images');
      var ul_new1 = ul_src.clone().appendTo('body');
      var ul_new2 = $(ul_src.prop('outerHTML')).appendTo('body');
      alert(
        'ul_src.height=' + ul_src.getHeight() + '\n' +
        'ul_new1.height=' + ul_new1.getHeight() + '\n' +
        'ul_new2.height=' + ul_new2.getHeight());
    });
  });
  $.extend(true, $.prototype, {
    getHeight: function() {
      return this.height();
    }
  });


以下は成功した時です。※時々、成功します。
成功

以下は失敗した時です。※大抵、失敗します。
イメージ説明

Chromeでは問題が起きないのですが、それはおそらく画像のキャッシュが働いているせいかと思います。
イメージ説明

Firefoxでは要素を複製する時に、img.src先をロードする処理が走り、そのタイムラグがあるせいだと思っていますが、ロードが完了するまで待つ良い方法がわかりません。
以下のコードに変えて試してみたのですが、「too much recursion」(再帰呼び出しオーバー)となってしまいます。

  $.extend(true, $.prototype, {
    getHeight: function() {
      var imgNum = this.find('img').length;
      var imgLoad = 0;
      var callback = this.getHeight.bind(this);
      while (imgLoad < imgNum) {
        this.find('img').each(function() {
          if (this.complete || /* for IE */ $(this).height > 0) {
            imgLoad++;
          }
        });
        if (imgLoad == imgNum) {
          break;
        }
        this.find('img').one('load', function() {
            callback();
        }).load();
        // もしくは、setTimeoutで少し待ってみる
        //setTimeout(callback, 100);
      }
      return this.height();
    }
  });

どのようにすれば、画像のロード完了を待った後に、要素の高さを取得することができるでしょうか?
詳しい方にご教授いただければ。よろしくお願いいたします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • naomi3

    2019/10/22 11:51

    当方も69.0.3 (64 ビット)で、設定もとくに変更していません。Windows7, DELLの古いPCです。

    キャンセル

  • nori2525w

    2019/10/22 11:56

    私は1年ほど前に買ったNEC製のWindows10のノートPCです。実行環境としては私の方が有利なんですね。なおさら謎になります。。

    キャンセル

  • nori2525w

    2019/10/29 15:55

    naomi3さま、その後の余談ですが、高さが正しく取得できなかったのは、Firefoxがメモリを浪費してメモリ使用量が99%近くに達していたからだろうと思います。マシンを再起動した後は再現しなかったので。メモリの空きが少なくなると、ブラウザのキャッシュが正常に機能しなくなるでしょうから、要素を複製すると画像を取りに行く羽目になって当然かもしれません。そうした状態になると、BAの方が教えて頂いたDeferredや、setTimeout、async/await、Promiseといった非同期のコールバック形式の「後追い」で値を取得するしか方法はないようです。JavaScriptは「シングルスレッド」なので。

    キャンセル

回答 1

checkベストアンサー

+2

ラグというのが再現させられなかったので不安なところはありますが、Deferred を使って画像のロードを待ってみるのがいいかと思います。

    getHeight: function() {
      var that = this;
      var deferred = $.Deferred();
      var promises = [];
      $('img', this).filter(function() { return !this.complete; }).each(function(index, element) {
        var deferred = $.Deferred();
        promises.push(deferred.promise());
        $(element).one('load', function() {
          deferred.resolve();
        });
      });

      $.when.apply(null, promises).then(function() {
        deferred.resolve(that.height());
      });

      return deferred.promise();
    }
      $.when(ul_src.getHeight(), ul_new1.getHeight(), ul_new2.getHeight()).then(function(src, new1, new2) {
        alert(
          'ul_src.height=' + src + '\n' +
          'ul_new1.height=' + new1 + '\n' +
          'ul_new2.height=' + new2);
      });


https://api.jquery.com/jQuery.Deferred/
https://sekom.hatenablog.com/entry/20130705/p1

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/10/24 14:54

    貴重なアドバイス、ありがとうございます!
    さっそく試してみたところ、問題は発生せず正常に高さを取得することができました!
    Deferredは全く知りませんでした。参考サイトへのリンクも貼っていただき感謝いたします。
    これを機会に改めて勉強します。ありがとうございました。m(_ _)m

    キャンセル

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

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

関連した質問

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