/* Chuyện học thư pháp: hiệu ứng, video, nghe bài viết cho khối #cht trong bài đăng Blogspot.
Nội dung bài luôn hiển thị khi không có file này; file này chỉ thêm tính năng. */
(function () {
"use strict";
var PLAY = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4 2.5v11l9-5.5z"/></svg>';
var PAUSE = '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="3" y="2" width="3.6" height="12" rx="1"/><rect x="9.4" y="2" width="3.6" height="12" rx="1"/></svg>';
var STOP = '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="3" y="3" width="10" height="10" rx="1.5"/></svg>';
function each(list, fn) { for (var i = 0; i < list.length; i++) fn(list[i], i); }
/* ---------- thanh tiến độ đọc ---------- */
function progress(root) {
if (document.getElementById("cht-tien-do")) return;
var bar = document.createElement("div");
bar.id = "cht-tien-do";
document.body.appendChild(bar);
function upd() {
var r = root.getBoundingClientRect();
var tot = r.height - window.innerHeight;
var p = tot > 0 ? Math.min(1, Math.max(0, -r.top / tot)) : 0;
bar.style.width = (p * 100) + "%";
}
window.addEventListener("scroll", upd, { passive: true });
upd();
}
/* ---------- hiện dần khi cuộn (không bao giờ giấu nội dung vĩnh viễn) ---------- */
function reveal(root) {
var items = root.querySelectorAll(".than > *, .moi, .khac, .md .net");
if (!("IntersectionObserver" in window)) return;
var vh = window.innerHeight;
var pending = [];
each(items, function (el) {
var r = el.getBoundingClientRect();
if (r.top > vh * 0.92) { el.classList.add("r"); pending.push(el); } // chỉ hiệu ứng cho phần chưa tới
else el.classList.add("in");
});
var fired = false;
var io = new IntersectionObserver(function (es) {
fired = true;
each(es, function (e) {
if (e.isIntersecting || e.boundingClientRect.top < 0) { e.target.classList.add("in"); io.unobserve(e.target); }
});
}, { rootMargin: "0px 0px -5% 0px", threshold: 0 });
each(pending, function (el) { io.observe(el); });
// dự phòng: nếu trình duyệt không gọi observer, hiện hết
setTimeout(function () { if (!fired) each(pending, function (el) { el.classList.add("in"); }); }, 1500);
// dự phòng khi nhảy bằng mục lục: phần đã cuộn qua cũng hiện
window.addEventListener("scroll", function () {
each(pending, function (el) { if (el.getBoundingClientRect().top < window.innerHeight) el.classList.add("in"); });
}, { passive: true });
}
/* ---------- video: bấm ảnh bìa mới tạo trình phát ---------- */
function video(root) {
each(root.querySelectorAll(".vd"), function (box) {
var link = box.querySelector("a.phat");
if (!link) return;
link.addEventListener("click", function (ev) {
ev.preventDefault();
var v = document.createElement("video");
v.src = link.getAttribute("href");
v.controls = true; v.autoplay = true; v.setAttribute("playsinline", "");
var img = box.querySelector("img");
if (img) v.poster = img.getAttribute("src");
var khung = box.querySelector(".khung");
khung.innerHTML = ""; khung.appendChild(v);
var p = v.play(); if (p && p.catch) p.catch(function () {});
if ("IntersectionObserver" in window) {
new IntersectionObserver(function (es) { if (!es[0].isIntersecting) v.pause(); }, { threshold: 0.2 }).observe(v);
}
});
});
}
/* ---------- nghe bài viết ---------- */
function reader(root) {
var slot = root.querySelector(".nghe");
if (!slot) return;
if (!("speechSynthesis" in window)) return; // không hỗ trợ thì không hiện thanh nghe
slot.innerHTML =
'<div class="box">' +
'<button type="button" class="doc" aria-label="Đọc bài viết">' + PLAY + '</button>' +
'<button type="button" class="dung phu" aria-label="Dừng đọc">' + STOP + '</button>' +
'<span class="nhan">Nghe bài viết</span><span class="thanh"><i></i></span>' +
'<select aria-label="Tốc độ đọc"><option value="0.85">Chậm</option><option value="1" selected>Vừa</option><option value="1.2">Nhanh</option></select>' +
'</div>';
slot.classList.add("co");
var btn = slot.querySelector(".doc"), stop = slot.querySelector(".dung"),
fill = slot.querySelector(".thanh i"), lab = slot.querySelector(".nhan"), sel = slot.querySelector("select");
var synth = window.speechSynthesis;
var nodes = [];
each(root.querySelectorAll(".md .lede, .than h2, .than h3, .than p, .than li, .than blockquote, .than .pull"), function (n) {
if (n.classList.contains("ky")) return;
if ((n.innerText || "").replace(/\s+/g, "").length > 2) nodes.push(n);
});
var i = 0, playing = false, cur = null;
function voice() {
var vs = synth.getVoices(), k;
for (k = 0; k < vs.length; k++) if (/^vi/i.test(vs[k].lang) && /Google|Natural|Premium|Enhanced|Linh|An/i.test(vs[k].name)) return vs[k];
for (k = 0; k < vs.length; k++) if (/^vi/i.test(vs[k].lang)) return vs[k];
return null;
}
function clear() { if (cur) cur.classList.remove("dang-doc"); }
function end() { playing = false; clear(); btn.innerHTML = PLAY; lab.textContent = "Đã đọc xong"; fill.style.width = "100%"; i = 0; }
function speak(k) {
if (k >= nodes.length) { end(); return; }
clear(); cur = nodes[k]; cur.classList.add("dang-doc");
try { cur.scrollIntoView({ block: "center", behavior: "smooth" }); } catch (e) {}
var u = new SpeechSynthesisUtterance(cur.innerText.replace(/\s+/g, " "));
var vo = voice(); if (vo) u.voice = vo;
u.lang = "vi-VN"; u.rate = parseFloat(sel.value || "1");
u.onend = function () { if (playing) { i = k + 1; fill.style.width = (i / nodes.length * 100) + "%"; speak(i); } };
u.onerror = function () { if (playing) { i = k + 1; speak(i); } };
synth.speak(u);
}
btn.onclick = function () {
if (playing) { synth.pause(); playing = false; btn.innerHTML = PLAY; lab.textContent = "Tạm dừng"; return; }
if (synth.paused && synth.speaking) { synth.resume(); playing = true; btn.innerHTML = PAUSE; lab.textContent = "Đang đọc…"; return; }
synth.cancel(); playing = true; btn.innerHTML = PAUSE; lab.textContent = "Đang đọc…"; speak(i);
};
stop.onclick = function () { synth.cancel(); playing = false; clear(); btn.innerHTML = PLAY; i = 0; fill.style.width = "0"; lab.textContent = "Nghe bài viết"; };
sel.onchange = function () { if (playing) { synth.cancel(); speak(i); } };
if (synth.onvoiceschanged !== undefined) synth.onvoiceschanged = voice;
window.addEventListener("beforeunload", function () { synth.cancel(); });
// khi cuộn qua: thu thành nút tròn ở góc dưới phải (không đụng menu cố định của theme)
function noi() {
var a = slot.getBoundingClientRect(), b = root.getBoundingClientRect();
slot.classList.toggle("noi", a.bottom < 0 && b.bottom > window.innerHeight * 0.6);
}
window.addEventListener("scroll", noi, { passive: true });
noi();
}
function init() {
each(document.querySelectorAll(".cht-bai"), function (root) {
if (root.getAttribute("data-san-sang")) return;
root.setAttribute("data-san-sang", "1");
root.classList.add("js");
try { progress(root); } catch (e) {}
try { video(root); } catch (e) {}
try { reader(root); } catch (e) {}
try { reveal(root); } catch (e) {}
});
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
else init();
})();
Nhận xét
Đăng nhận xét