水一篇文章 😤
看到群友说分享图无法生成,闲来无事于是就自己动手敲了个前端实现
这里通过录屏可以看到,该图由服务端生成并回传了一段 base64
给前端
实际测试,该操作还是会占用一部分的 CPU
资源,假如修改成前端生成,那么依赖的则只是客户端机器
So,JS 源代码如下
(() => {
const addScript = (url) => {
const scriptDom = document.createElement("script");
scriptDom.src = url;
document.head.appendChild(scriptDom);
};
addScript("https://file.qwq.link/js/html2canvas.min.js");
addScript("https://file.qwq.link/js/qrcode.min.js");
let container,
topDiv,
topImgContainer,
topImg,
topImgFilter,
dateContainer,
date,
yearMonth,
title,
description,
bottomDiv,
leftBotDiv,
logo,
logoDesc,
qrCodeDiv;
let siteLogo, siteTitle, siteDesc;
const DEFAULT_COVER = "/wp-content/themes/pix/img/banner.jpg";
let fontSize = {};
const cardDomInit = () => {
container = document.createElement("div");
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.backgroundColor = "white";
container.style.height = "100vh";
container.style.aspectRatio = "624/1035";
container.style.boxSizing = "border-box";
container.style.padding = "15px";
topDiv = document.createElement("div");
container.appendChild(topDiv);
topDiv.style.height = "80%";
topDiv.style.display = "flex";
topDiv.style.flexDirection = "column";
topDiv.style.justifyContent = "flex-end";
topDiv.style.position = "relative";
topDiv.style.padding = "30px";
topDiv.style.gap = "50px";
topImgContainer = document.createElement("div");
topDiv.appendChild(topImgContainer);
topImgContainer.style.position = "absolute";
topImgContainer.style.inset = "0";
topImgContainer.style.textAlign = "center";
topImg = document.createElement("div");
topImgContainer.appendChild(topImg);
topImg.style.backgroundSize = "cover";
topImg.style.backgroundPosition = "center";
topImg.style.position = "absolute";
topImg.style.inset = "0";
topImgFilter = document.createElement("div");
topImgContainer.appendChild(topImgFilter);
topImgFilter.style.position = "absolute";
topImgFilter.style.inset = "0";
topImgFilter.style.backgroundColor = "#00000050";
dateContainer = document.createElement("div");
topDiv.appendChild(dateContainer);
dateContainer.style.position = "absolute";
dateContainer.style.right = "20px";
dateContainer.style.top = "20px";
dateContainer.style.display = "flex";
dateContainer.style.flexDirection = "column";
dateContainer.style.color = "white";
date = document.createElement("span");
dateContainer.appendChild(date);
date.style.fontWeight = "800";
date.style.color = "white";
yearMonth = document.createElement("span");
dateContainer.appendChild(yearMonth);
yearMonth.style.color = "white";
yearMonth.style.marginLeft = "auto";
title = document.createElement("span");
topDiv.appendChild(title);
title.style.position = "relative";
title.style.fontWeight = "600";
title.style.color = "white";
description = document.createElement("div");
topDiv.appendChild(description);
description.style.position = "relative";
description.style.fontWeight = "400";
description.style.color = "#EBEFFE";
description.style.minHeight = "180px";
bottomDiv = document.createElement("div");
container.appendChild(bottomDiv);
bottomDiv.style.display = "flex";
bottomDiv.style.height = "0";
bottomDiv.style.padding = "20px";
bottomDiv.style.flexGrow = "1";
leftBotDiv = document.createElement("div");
bottomDiv.appendChild(leftBotDiv);
leftBotDiv.style.display = "flex";
leftBotDiv.style.flexDirection = "column";
leftBotDiv.style.justifyContent = "center";
leftBotDiv.style.gap = "10px";
leftBotDiv.style.flexGrow = "1";
logo = document.createElement("img");
leftBotDiv.appendChild(logo);
logo.style.maxHeight = "70px";
logo.style.maxWidth = "200px";
logo.style.objectFit = "cover";
logoDesc = document.createElement("span");
leftBotDiv.appendChild(logoDesc);
logoDesc.style.color = "#B4B4B4";
qrCodeDiv = document.createElement("div");
bottomDiv.appendChild(qrCodeDiv);
qrCodeDiv.style.height = "100%";
qrCodeDiv.style.aspectRatio = "1/1";
};
const loadSiteInfo = () => {
siteLogo = document.querySelector(".top_logo a img")?.src;
siteTitle = document.querySelector("title").innerText.split("-")[0];
siteDesc = document.querySelector(
".index_banner .user_info .des"
)?.innerText;
};
const withContent = ({
dateOfDay,
dateOfYearAndMonth,
cover,
cardTitle,
content,
siteLogo,
siteDesc,
}) => {
topImg.style.backgroundImage = `url(${cover})`;
date.textContent = dateOfDay;
yearMonth.textContent = dateOfYearAndMonth;
title.textContent = cardTitle;
description.textContent = content;
logo.src = siteLogo;
logoDesc.textContent = siteDesc;
};
const debounce = (fn, delay) => {
let timer = null;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay);
};
};
const base64ForCard = (data) => {
return new Promise((resolve) => {
withContent(data);
container.style.position = "absolute";
container.style.left = "-9999px";
document.body.appendChild(container);
qrCodeDiv.innerHTML = "";
new QRCode(qrCodeDiv, {
text: data.link,
height: qrCodeDiv.clientHeight,
width: qrCodeDiv.clientWidth,
});
html2canvas(container, {
logging: true,
letterRendering: 1,
allowTaint: true,
useCORS: true,
}).then((canvas) => {
document.body.removeChild(container);
resolve(canvas.toDataURL("image/png"));
});
});
};
const dataFromPost = (postId) => {
const posterBoxAp = document.querySelector(
`#share_modal_${postId} .poster_box_ap`
);
const postShareBox = document.querySelector(
`#share_modal_${postId} .post_share_box`
);
const images = document.querySelectorAll(
`#post-${postId} .img_list .list_inner a img`
);
let url = DEFAULT_COVER;
if (images != null && images.length > 0) {
const urlFromDom = images[0].src;
urlFromDom.indexOf(location.origin) !== -1 && (url = urlFromDom);
}
const contentDom = document.querySelector(
`#post-${postId} .entry-content .t_content p`
);
const linkDom = document.querySelector(
`#post-${postId} .entry-content .p_title a`
);
const timeDom = document.querySelector(
`#post-${postId} .list_user_meta .name time`
);
const date = new Date(timeDom.getAttribute("datetime"));
return {
posterBoxAp,
postShareBox,
data: {
dateOfDay: date.getDate(),
dateOfYearAndMonth: `${date.getFullYear()}/${date.getMonth() + 1}`,
cover: url,
cardTitle: `${siteTitle} - 片刻`,
content: contentDom.innerText,
link: linkDom.href,
siteLogo,
siteDesc,
},
};
};
const SayCardInit = () => {
document.querySelectorAll(".pix_share_btn").forEach((item) => {
item.onclick = null;
for (let i = 0; i < item.children.length; i++) {
item.children[i].onclick = null;
}
const originalA = item.children[0];
const postId = originalA.getAttribute("poster-data");
originalA.classList.remove("cr_poster");
item.onclick = () => {
const { posterBoxAp, postShareBox, data } = dataFromPost(postId);
base64ForCard(data).then((base64) => {
const div = document.createElement("div");
div.classList.add("poster_box");
div.style.border = "1px solid rgb(240, 240, 240)";
const img = new Image();
img.crossOrigin = "Anonymous";
img.src = base64;
div.appendChild(img);
posterBoxAp.innerHTML = "";
posterBoxAp.appendChild(div);
postShareBox.classList.remove("hide");
const last = postShareBox.lastElementChild;
last.href = base64;
last.setAttribute("download", `poster_${postId}.png`);
});
};
});
};
const windowSizeHanlder = () => {
if (window.innerWidth < 800) {
fontSize = {
date: "40px",
yearMonth: "12px",
title: "20px",
description: "14px",
logoDesc: "16px",
};
} else {
fontSize = {
date: "70px",
yearMonth: "18px",
title: "25px",
description: "18px",
logoDesc: "20px",
};
}
date.style.fontSize = fontSize.date;
yearMonth.style.fontSize = fontSize.yearMonth;
title.style.fontSize = fontSize.title;
description.style.fontSize = fontSize.description;
logoDesc.style.fontSize = fontSize.logoDesc;
};
cardDomInit();
window.addEventListener("DOMContentLoaded", () => {
windowSizeHanlder();
loadSiteInfo();
SayCardInit();
const postItem = document.querySelector("#post_item");
postItem?.addEventListener("DOMNodeInserted", () => {
debounce(() => {
SayCardInit();
}, 50)();
});
});
})();
不过用下来,我觉得体验不是很好,首先是没有动画,当然,多折腾一下就好了,但是我比较懒 🫠
实现上,我觉得这件事需要配合编辑 PHP
来完成,仅仅靠 JS
能做到,但是不是很优雅,比如代码中我通过 JS
移除了原有的 click
监听以及一段会导致请求调用的 attr
,然后重新加入了一个新的 click
监听...
不过对于使用者来说,仅 JS
还是会更加方便,毕竟只需要加入一个标签即可 🤔
Evan
介个是不是只能分享片刻,文章还是不行?哈哈哈哈哈。。我现在就是这样。字体也有些小了。哈哈
xiamo
@Evan 嗯,文章分享我没写,其实最好还是用主题原有的
Evan
@xiamo 嗯。