porpora
@porpora

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Javascriptでカーソルを合わせたらカーソルの横に説明を表示したいです

やりたいこと

動的に生成した文章内のキャラ名、アイテム名、セリフにカーソルを合わせたときに説明をカーソルの隣に表示。
カーソルを外した時は説明が消えるようにしたい。

今の状況

・カーソルを合わせたときに説明は出る
→元々HTMLに作成して非表示にしてある項目をoffsetを使用して移動して表示しているだけなので、ボタンとコンテンツの間に隙間が空いてしまう。

・カーソルを外すと一瞬説明が消えるがその後は再度表示され、ずっとカーソルの横に表示され続けている

初心者が練習がてら作っているものなのでかなりコードが長めで、なおかつ後で作ろうと放置している部分もございます。
足りない情報がありましたら申し訳ございません。
よろしくお願い致します。

今作ったコード

siren.html
<html lang="ja">
<head>
  'JQuery
  <script
    src="https://code.jquery.com/jquery-3.3.1.min.js"
    integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
    crossorigin="anonymous"></script>
  'siren.js読み込み
  <script type="text/javascript" src="siren.js"></script>
  <meta charset = "UTF-8">
  <link rel="stylesheet" href="siren.css">
  <title>練習</title>
</head>
<body class="box">
  <p class="tit">SIREN占い</p>
  '表示するシリーズ選択
  <form name="form1">
    <select name="limited" id="limited" size=1>
        <option value="one_only">無印のみ</option>
        <option value="two_only">2のみ</option>
        <option value="nt_only">ntのみ</option>
        <option value="all">全シリーズ</option>
    </select><span id="reload"></span>
        '何回表示するか
    <p><label for="number">何回占いをしますか?(1~5の間で入れてください)</label></p>
  <input type="number" name="number" id="number" min="1" max="5" value="1"></input><p><span id="caution"></span></p>
</form>

<button id="button_click">ジェノサイダー!</button>
<p></p>
<div id="wrapper">
  <div class="explain">
  </div>
  <p>
    '結果を入れる
    <span class="change" id="change"></span>
  </p>
</div>
<fotter>
参考資料:各種bot 実況者様の動画など
</fotter>
</body>

</html>
siren.js

$(function() {
let series;
//キャッチコピー
let tagline = ["どうあがいても、絶望", "逃げ場なんて、ないよ", "息をすることさえ、恐怖"];
//無印キャラ
let one_character = ["須田恭也", "神代美耶子", "神代亜矢子", "神代淳", "八尾比沙子", "志村晃", "志村晃一", "竹内多聞", "安野依子", "牧野慶", "宮田司郎", "恩田美奈", "恩田理沙", "高遠玲子", "四方田春海", "美浜奈保子", "前田知子"];
//2キャラ
let two_character = ["一樹守", "木船郁子", "多河柳子", "矢倉市子", "永井頼人", "沖田宏", "三沢岳明", "阿部倉司", "三上脩", "三上隆平", "岸田百合", "加奈江", "藤田茂", "喜代田章子", "太田ともえ", "太田常雄","須田恭也"];
//NTキャラ
let nt_character = ["ハワード・ライト", "美耶古", "サム・モンロー", "メリッサ・ゲイル", "ベラ・モンロー", "犀賀省悟", "アマナ", "ソル・ジャクソン", "河辺幸江"];
//無印アイテム
let one_item = ["火掻き棒", "ネイルハンマー", "ラチェットスパナ", "テレフォンカード", "発煙筒", "ヴェール", "壊れたラジオ", "拳銃", "木製バット", "ライター", "笑い袋", "ビーズ人形", "焔薙", "図書貸し出しカード", "写真", "宇理炎", "", "", ""];
//2アイテム
let two_item = ["壊れた朱石のブレスレット", "壊れた碧石のブレスレット", "釘バット", "メダル", "髪飾り", "首輪", "奇石", "週刊アトランティス", "TNT", "", "軍用スコップ", "あんなき", "ゴルフクラブ", "空き缶"];
//NTアイテム
let nt_item = ["一升瓶", "アナログテレビ", "改造散弾銃", "案内板", "ビデオカメラ", "缶切り", "携帯電話", "", "アンテナ", "ブローチ", "柄杓", "宇理炎", "つるはし", "トロッコ", "ハニュウダカブト"];
//無印セリフ
let one_quotation = Quotation_One();
//2セリフ
let two_quotation = Quotation_Two();
//NTセリフ
let nt_quotation = Quotation_Nt();
//背景色をシリーズごとに変更するためキャラ名、アイテム名、セリフのシリーズを入れる
let item_color;
let character_color;
let quotation_color;

//シリーズ名を入れる(全シリーズ選択時に使用)
let series_all = ["one", "two", "nt"];
//CSS作成用
let reload_css = {
  "background-color": "#A4C6FF",
  "border":"1px solid #000033",
  "padding": "3px 5px",
  "border-radius": "3px",
  "cursor": "pointer",
  "user-select": "none",
  "-moz-user-select": "none",
  "-webkit-user-select": "none",
  "-ms-user-select": "none",
  "unselectable": "on"
}
//CSS削除用
let delete_css = {
  "background-color": "",
  "padding": "",
  "border-radius": "",
  "cursor": "",
  "user-select": "",
  "-moz-user-select": "",
  "-webkit-user-select": "",
  "-ms-user-select": "",
  "unselectable": ""
}
//無印のカラーCSS(薄いピンク)
let color1 = {
    "background-color":"#FFCCFF",
  "display":"inline-block"
}
//2のカラーCSS(薄い青)
let color2 = {
    "background-color":"#A4C6FF",
  "display":"inline-block"
}
//NTのカラーCSS(薄いオレンジ)
let color3 = {
    "background-color":"#FFDBC9",
  "display":"inline-block"
}

//シリーズ名を選択したらボタンの色を変更
$("#limited").change(function() {
  $("#reload").html("");
  $("#reload").css(delete_css);
  series = $("#limited").val();
  switch (series) {
    case "one_only":
      $("#button_click").text("ジェノサイダー!");
      break;

    case "two_only":
      $("#button_click").text("神風見せてやるよ!");
      break;

    case "nt_only":
      $("#button_click").text("Go To Hell!");
      break;

    default:
      change_tagline();
      all_reload();
      break;
  }
});



//全シリーズ選択時にランダムにゲームのキャッチコピーを入れる
function change_tagline() {
  $("#button_click").text(tagline[Math.floor(Math.random() * tagline.length)]);
}

//変更ボタンを押した時の処理
function all_reload() {
  //変更ボタンを作成
  $("#reload").html("<div id='change_link'>変更</div>");
  //cssを作成(背景色を付け、クリックしても選択状態にならないようにした)
  $("#reload").css(reload_css);

}

//変更ボタンをクリックしたときにキャッチコピー変更メソッドを呼び出す
$("#reload").click(function() {
  change_tagline();
});

//占い結果を取得
$("#button_click").click(function() {
    if($("number").val() > 5 || $("number").val() < 1){
    $("caution").val("範囲外です");
  }else{
    series = $("#limited").val();
    let result = "";

      for (let i = 0; i < $("#number").val(); i++) {
        result += fortune_telling(i);
        $("#change").html(result);
        $(".border").css("margin", "50px");
        $(".1").css(color1);
        $(".2").css(color2);
        $(".3").css(color3);
        $(".explain").text("ここは説明文");
        $(".explain").css({
          "width":"30%",
          "border":"1px dashed #6AC1B7",
          "background-color":"#BFE9DB",
          "margin-left":"35%"
        });


    }
  }

});

//hoverで説明を表示したい
$(document).on({ 
  "mouseenter": function(){
   $(window).on('mousemove', function(e) {
      //座標のXとYを取得したうえで調節
      let X = e.clientX + 20;
      let Y = e.clientY - 5;
      //移動させる
      $(".explain").offset({top: Y, left: X});
      //フェードイン
      $(".explain").fadeIn();
        });
  },

  "mouseleave": function(){
    //CSSをnoneに変更
    $(".explain").css("display", "");
  }
}, ".contents");

//占い結果を返す
function fortune_telling(num) {
  let character;
  let item;
  let quotation;
  //キャラ名とアイテム名を入れる
  switch (series) {
    //無印のみの場合は無印の中から取得
    case "one_only":
      character = one_character[Math.floor(Math.random() * one_character.length)];
      item = one_item[Math.floor(Math.random() * one_item.length)];
      quotation = one_quotation[Math.floor(Math.random() * one_quotation.length)];
      item_color = 1;
      character_color = 1;
      quotation_color = 1;
      break;

      //2のみの場合は2の中から取得
    case "two_only":
      character = two_character[Math.floor(Math.random() * two_character.length)];
      item = two_item[Math.floor(Math.random() * two_item.length)];
      quotation = two_quotation[Math.floor(Math.random() * two_quotation.length)];
      item_color = 2;
      character_color = 2;
      quotation_color = 2;
      break;

      //NTのみの場合はNTの中から取得
    case "nt_only":
      character = nt_character[Math.floor(Math.random() * nt_character.length)];
      item = nt_item[Math.floor(Math.random() * nt_item.length)];
      quotation = nt_quotation[Math.floor(Math.random() * nt_quotation.length)];
      item_color = 3;
      character_color = 3;
      quotation_color = 3;
      break;

      //それ以外(全シリーズ)の場合はランダムに取得
    default:
      //キャラとアイテムをそれぞれどのシリーズから取得するかランダムに設定
      let character_series = series_all[Math.floor(Math.random() * series_all.length)];
      let item_series = series_all[Math.floor(Math.random() * series_all.length)];
      let quotation_series = series_all[Math.floor(Math.random() * series_all.length)];
      //アイテム名とキャラ名を変数に格納
      if (character_series == "one") {
        character = one_character[Math.floor(Math.random() * one_character.length)];
        character_color = 1;
      } else if (character_series == "two") {
        character = two_character[Math.floor(Math.random() * two_character.length)];
        character_color = 2;
      } else {
        character = nt_character[Math.floor(Math.random() * nt_character.length)];
        character_color = 3;
      }

      if (item_series == "one") {
        item = one_item[Math.floor(Math.random() * one_item.length)];
        item_color = 1;
      } else if (item_series == "two") {
        item = two_item[Math.floor(Math.random() * two_item.length)];
        item_color = 2;
      } else {
        item = nt_item[Math.floor(Math.random() * nt_item.length)];
        item_color = 3;
      }

      if (quotation_series == "one") {
        quotation = one_quotation[Math.floor(Math.random() * one_quotation.length)];
        quotation_color = 1;
      } else if (quotation_series == "two") {
        quotation = two_quotation[Math.floor(Math.random() * two_quotation.length)];
        quotation_color = 2;
      } else {
        quotation = nt_quotation[Math.floor(Math.random() * nt_quotation.length)];
        quotation_color = 3;
      }
  }
  //すべての色が同じ(全部同じシリーズ)だった場合大吉か確認する
  if(item_color == quotation_color && item_color == character_color){
    confirm(item, character, quotation, item_color);
  }

  //文字列に文面を入れる
  return "<div id='fortune-telling'><p>今日のあなたにぴったりなキャラクターは<span class='contents " + character_color + "'>" + character + "</span>です!</p><p>ラッキーアイテムは<span class='contents " + item_color + "'>" + item + "</span>です!</p><p class='border'>今日の一言</p><p class='contents " + quotation_color + "'>「" + quotation + "」</p></div><p class='border'>----------<p>";
}

//大吉確認用(引数はアイテム名、キャラ名、セリフ、シリーズ内のどれか)
function confirm(item, character, quotation, confirm_series){
    let confiem_goodluck = character + "_" + item + "_" + quotation;
}

//セリフ生成メソッド(無印)
function Quotation_One() {
  let kyoya = ["お前じゃなくて、須田恭也", "悪いけど、美耶子との約束なんだ", "そんで、この村からも逃げ出そう"];
  let miyako = ["恭也…ありがとう", "ぐーず", "ケルブじゃないとやっぱりだめ…", "早く連れてけ!", "私の目を使って", "恭也…約束、したよね?", "全部消して。この村も、あいつらも全部!"];
  let jun = ["昨日のお返しだよ", "美耶子、お前の役割はまだ終わってないだろう?", "どうして亜矢子まで!", "誰だ!"];
  let ayako = ["違う…私は違う…!", "あんたが生贄の羊になりなさいよ!", "淳は、私の許嫁なんだから!"];
  let yao = ["この水があなたの体内に入ったの…流した血の分だけ", "あなたが実を盗んだのね!", "もう、待つのは嫌…!", "奇妙に思うかもしれないけれど、これが私たちの信仰なの"];
  let makino = ["八尾さん…どこに行ったんだ?", "私は…ただのみじめな道化だった", "狂ってる…!", "知子ちゃん!", "きゅるってる!"];
  let miyata = ["私も美奈さんを探していたんだ。とりあえず一緒に病院に", "っ馬鹿な…!", "それでも俺は、牧野さんになりたかった", "煉獄の炎よ。俺の命と引き換えだ", "あぁ…今行くよ"];
  let risa = ["お姉ちゃん!お姉ちゃんなの!?", "もしかして、宮田先生?私、恩田美奈の妹です!理沙です!", "お姉ちゃんはどこなんですか!?お姉ちゃんは無事なんですか!?", "そっくり!先生も双子?", "お姉ちゃん男なんですか!?お姉ちゃん武士なんですか!?"];
  let akira = ["お前が呼んだのか?", "よそ者か…迷い込んだのか?", "竹内か…", "この村ももう終わりだ…", "結局逃げきれないということか"];
  let mihama = ["こっそりカメラ回してるんじゃないでしょうね!", "永遠の若さ…永遠の若さ…"];
  let tomoko = ["求導師様、どうしたんですか?", "はーるみちゃんがほーしい", "お父さん!お母さん!開けてよ!"];
  let tamon = ["志村さん…なのか…?", "車にいろと言ったはずだ", "しかし、とんだ里帰りに君を巻き込んでしまったな", "お父さん、お母さん…!"];
  let yoriko = ["先生、家族ごっこしてる場合ですか!", "無理やりついてきたのは私ですよ?", "とりあえず村の人探しましょうよ。きっと大騒ぎですよ!", "先生、それって…本物?"];
  let takato = ["先生が絶対に助けてあげるからね…!", "春海ちゃん!だめ…諦めたらだめ…!", "春海ちゃん、どこぉ…?", "めぐみ…お母さんを許して…", "じゃあ、先生はお母さんになってあげる。春海ちゃんの"];
  let harumi = ["お母さん…", "先生、春海怖いよ…", "うん…春海、待ってる", "先生、助けて!先生!早く来て!"];
  let ishida = ["無駄な抵抗は止めなさい", "了解…射殺します"];
  return kyoya.concat(miyako, jun, ayako, yao, makino, miyata, risa, akira, mihama, tomoko, tamon, yoriko, takato, harumi, ishida);
}
//let 名前 = ["","","","",""];
//セリフ生成メソッド(2)
function Quotation_Two() {
  let ituki = ["クソ…俺は、だまされたのか?", "化け物め…化け物め…化け物め…!", "人にものを頼むときはお願いします、だろ?", "やつの動きを止めるんだ!", "俺という観測者が到達したとき、可能性は一つに収束する"];
  let ikuko = ["もう一人の私…こんな思いしないのかな…?", "結局この島に引き寄せられちゃったよ、お母さん…", "先に…私たちが先にたどり着かないと!", "そんな…化け物見るような眼で見ないでよ…", "こんなところでかっこつけたって誰も見てないんだから!"];
  let nagai = ["神風見せてやるよ!", "沖田さん!死なないでください…沖田さん…!", "人に助けてもらったときは何て言うんだっけ?", "逆切れの方が強ぇーんだよ"];
  let misawa = ["士長、応戦だ", "俺だけ先に目覚めちゃうけど…悪いな", "久しぶりに最高の気分だ", "あっち側もこっち側も関係ない", "なーがいくん!一緒に遊びましょー!"];
  let itiko = ["わからない…わからないよ…", "お寝坊さんなお姉ちゃん", "お前もずっと寂しかったんだろう?", "違う…私、あの時死んだ!私は私じゃない!", "みーつけた!"];
  let kanae = ["脩…お姉ちゃんを許して…", "脩、どこにいるの?", "七つの門の七つの鍵を開けるの", "お休み、脩"];
  let yuri = ["あなたは、私を信じてくれる?", "お母さんは鳩を飛ばし続けてたの", "私を見て…"];
  let shu = ["ツカサなのか…?これは、お前の…", "お姉ちゃん!", "お姉ちゃんもお外で遊べたらいいのにね", "見える…見えるよ……"];
  let abe = ["これ…純金じゃねぇのか?", "クソすぎだろ!このままじゃよぉ…"];
  let ryuko = ["あなた、お母さんと同じ気配…お母さん、目覚めようとしてるのね?", "ごめんね…"];
  let ryuhei = ["化け物なんかじゃないですよ", "おーい、かくれんぼか?", "学会に発表しなきゃなぁ"];
  let hujita = ["やんなっちまうなぁ…", "すまんなぁ、朝子", "検挙するぞ!", "始末書…始、末書"];
  let kiyota = ["なんだか最悪の誕生日", "あぁ…たくさんの強い念が…", "私だってペーパーなんだから!", "…分かった。信じる"];
  let tomoe = ["髪飾り…お父様にもらった、髪飾り…", "ここにいる!化け物女がここにいる!", "誰か来て!誰かー!", "虫けらのくせに!", "髪飾り返してよ!", "都会ってどんなところなのかなぁ"];
  let tuneo = ["お前たち、やってくれるな?", "藤田のバカ息子が…結局逃げかえってきたか"];
  let okita = ["永井も随分と冷たいよなぁ", "永井ぃ!俺だよ俺ぇ!", "永井ー。根性出せよぉ"];
  let kyoya = ["お前らみたいなのがいる限り、俺は何度でも現れる"];
  return ituki.concat(ikuko, nagai, misawa, itiko, kanae, yuri, shu, abe, ryuko, ryuhei, hujita, kiyota, tomoe, tuneo, okita,kyoya);
}

//セリフ生成メソッド(NT)
function Quotation_Nt() {
  let haward = ["Miyako, I promise.", "ボクは、ハワード。君は?", "ミヤコ、イッショ、ガンバル!"];
  let saiga = ["運命に抗ってみますか?ご自由に", "幸江…", "俺はダンテではない。ベアトリーチェの導きは期待できない", "もうあきらめろ。無駄だ。流れに身を任せろ", "さすがに飽きた"];
  let bella = ["Daddy said you can make bugs show up if you put something sweet on a tree...(ダディが言ってた 木に蜜を塗ったら虫が来るって)"];
  let mellisa = ["When this thing died, the others did too.(こいつが死んだら…あいつらも死んだ?)"];
  let yukie = ["先生は…私がいないと、だめなんだからぁ", "先生ぇ…"];
  let sam = ["Hey! Does that works? Maybe we can use it to call for help!(おい!その電話は使えないのか?その電話で助けを呼んでくれよ!)"];
  let sol = ["Melissa,Bella...I hope you guys are okay.(メリッサ、ベラ…無事でいてくれよ…)"];
  let amana = ["Perhaps we can set a trap.(罠を仕掛けられないでしょうか?)"];
  let miyako = ["死にたくない!一緒に逃げたい!村の外を見てみたい、こんな村大嫌い…!", "…早く連れてけ", "いや!死ぬなんて絶対に嫌!", "こんなことしかできない。…ごめんね、せっかく綺麗だったのに"];
  return haward.concat(saiga, bella, mellisa, yukie, sam, sol, amana, miyako);
}
//大吉を"キャラ名_アイテム名_セリフ"の形式で入れていく(この下は後で作る)
let GoodLuck_1 = ["須田恭也_火掻き棒_お前じゃなくて、須田恭也", "","神代亜矢子_鎌_あんたが生贄の羊になりなさいよ!","神代淳_猟銃_昨日のお返しだよ","牧野慶_ヴェール_八尾さん…どこに行ったんだ?","安野依子_木製バット_先生、家族ごっこしてる場合ですか!","","","","","","","","","","","","",""];

let GoodLuck_2 = ["須田恭也_宇理炎_お前らみたいなのがいる限り、俺は何度でも現れる","","","","","","","","","","","","","","","","","","","",""];

let GoodLuck_3 = ["","","","","","","","","","","","","","","","","","","","",""];
});
0

1Answer

カーソルを外した時は説明が消えるようにしたい。

のであれば、以下の修正を加えます。

siren.js(修正前)

     (省略)

//hoverで説明を表示したい
$(document).on({ 
  "mouseenter": function(){
   $(window).on('mousemove', function(e) {
      //座標のXとYを取得したうえで調節
      let X = e.clientX + 20;
      let Y = e.clientY - 5;
      //移動させる
      $(".explain").offset({top: Y, left: X});
      //フェードイン
      $(".explain").fadeIn();
        });
  },

  "mouseleave": function(){
    //CSSをnoneに変更
    $(".explain").css("display", "");
  }
}, ".contents");

//占い結果を返す

     (省略)

の部分を修正します。

現在のソースだと、コンテンツの説明(以下、ツールチップ)がカーソルが移動する度にfadeInされてしまうためコンテンツにカーソルを重ねた場合のみにツールチップを表示する処理を追加します。

カーソルが要素と重なる(mouseover) -> ツールチップを表示
カーソルを移動させる(mousemove) -> ツールチップを追従
カーソルが要素から外れる(mouseleave) -> ツールチップを非表示

siren.js(修正後)

     (省略)

//hoverで説明を表示したい
$(document).on({ 
  "mouseover": function(){
      //ツールチップ表示
      $(".explain").css("visibility", "visible");
  },
  "mousemove": function(e) {
      //座標のXとYを取得したうえで調節
      let X = e.clientX + 20;
      let Y = e.clientY - 5;
      //移動させる
      $(".explain").offset({top: Y, left: X});
  },
  "mouseleave": function(){
    //ツールチップ非表示
    $(".explain").css("visibility", "hidden");
  }
}, ".contents");

//占い結果を返す

     (省略)

また非表示の際に、display: none;を指定すると本来要素が存在していた領域が非表示となってしまい、それより下の要素が詰めて表示される為visibility: hidden;を利用して非表示にすると良いです。

説明下手で申し訳ないです。

1Like

Comments

  1. @porpora

    Questioner

    ありがとうございます!
    無事にできました。

Your answer might help someone💌