Edit in JSFiddle

// ページの読み込みが終わったら、地図表示の処理を行う
window.addEventListener("DOMContentLoaded", function () {   // ■(B)
    const posts = JSON.parse(document.getElementById("data").innerHTML);

    // カテゴリーID とマーカー画像の対応
    const icon_map = {
        2: "http://dev.openlayers.org/img/marker.png",
        3: "http://dev.openlayers.org/img/marker-blue.png",
        4: "http://dev.openlayers.org/img/marker-gold.png",
        5: "http://dev.openlayers.org/img/marker-green.png",
    };

    const epsg4326 = ol.proj.get('EPSG:4326');
    const epsg3857 = ol.proj.get('EPSG:3857');

    // マーカー(ol.Feature)の生成
    const features = posts.map(post => {
        const lonlat = [post.lng, post.lat];
        return new ol.Feature({     // ■(H)-1
            geometry: new ol.geom.Point(ol.proj.transform(lonlat, epsg4326, epsg3857)),
            post: post,         // ■(I)-2
        });
    });

    // マーカーとツールチップを、店舗情報毎に設定するための Layer
    const marker_layer = new ol.layer.Vector();

    marker_layer.setSource(new ol.source.Vector({features: features}));  // ■(H)-2
    marker_layer.setStyle(function(feature, resolution) {
        const post = feature.get("post");
        return [new ol.style.Style({
            image: new ol.style.Icon({
                anchor: [0.5, 1],
                src: icon_map[post.cat_id],     // ■(I)-1
            }),
        })];
    });

    const view = new ol.View();

    const map = new ol.Map({        // ■(A)
        target: 'map',          // ■(C)
        view: view,
//      renderer: "webgl",
//      interactions: ol.interaction.defaults()
    });

    map.addLayer(new ol.layer.Tile({ source: new ol.source.OSM() }));       // ■(A)
    map.addLayer(marker_layer);                               // ■(H)-3

    map.addControl(new ol.control.ScaleLine());     // ■(E)

    // マーカーが含まれる範囲の Extent と、それに合わせたズーミング
    let ext = ol.extent.boundingExtent(posts.map(p => [p.lng, p.lat]));     // ■(F)-2
    ext = ol.proj.transformExtent(ext, epsg4326, epsg3857);
    view.fit(ext, map.getSize());       // ■(D)、(F)-1

    // ズームの最大値は 16 まで // ■(G)
    if (view.getZoom() > 16) {
        view.setZoom(16);
    }

    let popup_z_index = 9000;

    // 吹き出しの表示、非表示を切り替える関数
    function toggle_popup(feature, coordinate) {
        let popup = feature.get("popup");
        if (! popup) {
            const post = feature.get("post");
            const e = Object.assign(document.createElement("div"), {    // ■(K)-1
                className: "ol-popup",
                innerHTML: `
                    <a href="#" class="ol-popup-closer"></a>
                    <div class="ol-popup-content">
                        ${post.name} : <a href="${post.link}">${post.title}</a>
                    </div>
                `,
                ol_feature: feature,
            });
            // addOverlay() は、.ol-overlay-container の先頭に insert していくので、
            // 後で表示した方を上に出すように、z-index を調整する
            e.style.zIndex = ++popup_z_index;
            popup = new ol.Overlay({
                element: e,
                autoPan: true,
                autoPanAnimation: {
                    duration: 250,
                },
            });
            feature.set("popup", popup);
            e.querySelector(".ol-popup-closer").addEventListener("click", ev => {
                toggle_popup(ev.target.parentNode.ol_feature);
                ev.target.blur();
                ev.preventDefault();
                ev.stopPropagation();
            });
            map.addOverlay(popup);
            popup.setPosition(feature.getGeometry().getFirstCoordinate());
            popup.setPosition(coordinate);

        } else {
            map.removeOverlay(popup);
            feature.unset("popup");
        }
    }
    map.on("click", evt => {
        // クリックした位置にマーカーがあったら、吹き出しの表示、非表示を切り替える
        const features = map.getFeaturesAtPixel(evt.pixel);     // ■(L)-1
        if (features) {
            toggle_popup(features[0], evt.coordinate);
        }
    });

    
    // マーカーにツールチップを表示するためのスタイル
    const tooltipStyle = function(feature, resolution) {
        const post = feature.get("post");
        return new ol.style.Style({
            image: new ol.style.Icon({
                anchor: [0.5, 1],
                src: icon_map[post.cat_id],     // ■(I)-1
            }),
            text: new ol.style.Text({   // ■(J)-1
                text: post.title,
                backgroundFill: new ol.style.Fill({
                    color: "white",
                }),
                padding: [2,2,2,2],
                textAlign: "left",
                offsetX: 10,
                offsetY: -30,
            }),
        });
    };

    map.on('pointermove', function(evt) {
        if (evt.dragging) {
            return;
        }
        // マーカーのツールチップを消す
        marker_layer.getSource().getFeatures().forEach(f => {
            f.setStyle(null);
        });
        // マウスポインタがある位置のマーカーに、ツールチップを表示するスタイルを指定する
        const pixel = map.getEventPixel(evt.originalEvent);
        const feature = map.forEachFeatureAtPixel(pixel, function(feature) {    // ■(J)-2
            feature.setStyle(tooltipStyle);
            return feature;
        });    

        // マーカー上にマウスポインタがあれば、カーソルのスタイルを変更する
        const target = document.getElementById(map.getTarget());
        target.style.cursor = feature ? "pointer" : "";
    });

});


<div id="map" style="width: 99%; height: 650px;"></div>


<script id="data" type="text/json">
[
  {
    "lat": 35.701418,
    "lng": 139.74787,
    "name": "和食料理店",
    "link": "http://www.google.com/search?q=%e5%92%8c%e9%a3%9f%e5%89%b2%e7%83%b9zen",
    "title": "和食割烹ZEN",
    "cat_id": "2"
  },
  {
    "lat": 35.6950886,
    "lng": 139.74937320000004,
    "name": "犬猫病院",
    "link": "http://www.google.com/search?q=%e3%82%a2%e3%83%8b%e3%83%9e%e3%83%ab%e3%83%9b%e3%82%b9%e3%83%94%e3%82%bf%e3%83%ab",
    "title": "アニマルホスピタル",
    "cat_id": "5"
  },
  {
    "lat": 35.67694669999999,
    "lng": 139.7635034,
    "name": "カフェ",
    "link": "http://www.google.com/search?q=%e3%82%b9%e3%82%a4%ef%bc%8d%e3%83%84%e3%83%99%e3%83%aa%ef%bc%8d",
    "title": "スイ-ツベリ-",
    "cat_id": "2"
  },
  {
    "lat": 35.6964531,
    "lng": 139.77445769999997,
    "name": "服屋",
    "link": "http://www.google.com/search?q=%e3%83%a6%e3%83%8b%e3%82%b7%e3%83%ad",
    "title": "ユニシロ",
    "cat_id": "4"
  },
  {
    "lat": 35.6964946,
    "lng": 139.77230780000002,
    "name": "不動産屋",
    "link": "http://www.google.com/search?q=%e3%82%a8%e3%82%b9%e3%83%86%ef%bc%8d%e3%83%88%e6%9d%b1%e4%ba%ac",
    "title": "エステ-ト東京",
    "cat_id": "3"
  },
  {
    "lat": 35.6945504,
    "lng": 139.770258,
    "name": "たこ焼き屋",
    "link": "http://www.google.com/search?q=%e3%81%8f%e3%82%8b%e3%81%8f%e3%82%8b",
    "title": "くるくる",
    "cat_id": "2"
  },
  {
    "lat": 35.6963474,
    "lng": 139.77209949999997,
    "name": "ペット",
    "link": "http://www.google.com/search?q=%e3%83%9a%e3%83%83%e3%83%88%e3%82%b7%e3%83%a7%e3%83%83%e3%83%97%e3%82%8f%e3%82%93%e3%81%ab%e3%82%83%e3%82%93",
    "title": "ペットショップわんにゃん",
    "cat_id": "5"
  },
  {
    "lat": 35.6950947,
    "lng": 139.7685136,
    "name": "工務店",
    "link": "http://www.google.com/search?q=%e3%83%88%e3%83%b3%e3%82%ab%e3%83%81%e5%b7%a5%e5%8b%99%e5%ba%97",
    "title": "トンカチ工務店",
    "cat_id": "3"
  },
  {
    "lat": 35.6971917,
    "lng": 139.76922739999998,
    "name": "お好み焼き",
    "link": "http://www.google.com/search?q=%e3%81%98%e3%82%85%e3%81%86%e3%81%98%e3%82%85%e3%81%86",
    "title": "じゅうじゅう",
    "cat_id": "2"
  },
  {
    "lat": 35.6749887,
    "lng": 139.76436450000006,
    "name": "美容室",
    "link": "http://www.google.com/search?q=%e3%82%b5%e3%83%a9%e3%82%b5%e3%83%a9%e7%be%8e%e5%ae%b9%e5%ae%a4",
    "title": "サラサラキュ-ティクル",
    "cat_id": "4"
  },
  {
    "lat": 35.6817879,
    "lng": 139.76676139999995,
    "name": "パン屋",
    "link": "http://www.google.com/search?q=%e3%83%91%e3%83%b3%e5%b1%8b%e3%82%a4%ef%bc%8d%e3%82%b9%e3%83%88",
    "title": "イ-ストパン",
    "cat_id": "2"
  }
]
</script>
#map {
	/* (N) */
	width: 99%;
	height: 650px;
}

/* ■(K)-2 */
.ol-popup {
	position: absolute;
	background-color: white;
	-webkit-filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
	filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
	padding: 15px;
	border-radius: 10px;
	border: 1px solid #cccccc;
	bottom: 12px;
	left: -50px;
	min-width: 280px;
}
.ol-popup:after, .ol-popup:before {
	top: 100%;
	border: solid transparent;
	content: " ";
	height: 0;
	width: 0;
	position: absolute;
	pointer-events: none;
}
.ol-popup:after {
	border-top-color: white;
	border-width: 10px;
	left: 48px;
	margin-left: -10px;
}
.ol-popup:before {
	border-top-color: #cccccc;
	border-width: 11px;
	left: 48px;
	margin-left: -11px;
}
.ol-popup-closer {
	text-decoration: none;
	position: absolute;
	top: 2px;
	right: 8px;
}
.ol-popup-closer:after {
	content: "\2716";
}