关于在博客页面添加live2d-widget的一些心得和踩过的坑
如何在自己的博客页面使用自定义Live2D模型
在Hugo站点(我使用的是Reimu主题,其他主题也是同理的)中集成自定义Live2D模型,需要配置资源加载、处理CDN问题和大文件等。下面是详细步骤,基于我的实际经验(包括模型加载失败、CDN加载失败、GitHub Raw 400错误和大文件限制等等问题)。
1: 准备Live2D资源
-
创建GitHub仓库:新建一个公开仓库(如
your_github_username/live2d_api
),下载live2d-api仓库的model文件夹以及model.json文件上传到新建仓库。后续对应的文件目录结构也要参考这个库下面的教程。
下载live2d-widget库下dist文件夹的所有内容,上传到仓库。确保分支为main
。
如果要上传自己的Live2D模型文件(.moc3、.json、纹理图片等),只需要薅来的模型文件中把model.json或者说model3.json改名为index.json就可以了,但是注意命名不要有中文,会出现乱码。当然上传了新模型的话,对应的modellist也需要对应修改,这部分很简单,可以直接参考live2d-api仓库的教程。 -
关键文件:
waifu-tips.json
:交互框内容。(我是下载下来按照自己喜好修改了,如果你觉得原来的也行的话,就用源仓库的也行)。waifu.css
和waifu-tips.js
:样式和模型加载脚本。modellist.json
:模型存放信息,用于加载模型。
-
关于访问:
每次上传了新的模型,需要点击该仓库的tag,然后新建release,只有这样后续才能使用cdn链接加载你的模型。
注意:仓库必须公开,否则Raw访问会返回400 Bad Request。如果你想吃现成的话当然也可以直接照抄我的仓库,也有一些我嫖到的免费模型:live2d_api
2: 配置Reimu主题中的Live2D
Reimu主题内置Live2D支持(在themes/reimu/static/resources/live2d-widgets@0.9.0/autoload.js
)。别的主题也应该有,如果没有的话可以把live2d-widget仓库下载下来把autoload.js保存到跟这个主题对应的位置。我的autoload.js是已经修改过的,所以说可能和原版不太一样,如果原版的不太行的话,可以尝试一下我的:
// live2d_path 参数建议使用绝对路径
const live2d_path = "https://fastly.jsdelivr.net/npm/live2d-widgets@1.0.0-rc.7/dist/";// 封装异步加载资源的方法
function loadExternalResource (url, type) {return new Promise((resolve, reject) => {let tag;if (type === "css") {tag = document.createElement("link");tag.rel = "stylesheet";tag.href = url;}else if (type === "js") {tag = document.createElement("script");tag.type = 'module';tag.src = url;}if (tag) {tag.onload = () => resolve(url);tag.onerror = () => reject(url);document.head.appendChild(tag);}});
}// 避免跨域图片资源问题
const OriginalImage = window.Image;
window.Image = function (...args) {const img = new OriginalImage(...args);img.crossOrigin = "anonymous";return img;
};
window.Image.prototype = OriginalImage.prototype;// 加载 waifu.css 和 waifu-tips.js
(async () => {// 备用CDN列表,如果主CDN无法访问会自动尝试备用CDNconst cdnList = ["https://fastly.jsdelivr.net/gh/nizigen/live2d_api@v1.4/","https://cdn.jsdelivr.net/gh/nizigen/live2d_api@v1.4/","https://raw.githubusercontent.com/nizigen/live2d_api/v1.4/","https://gcore.jsdelivr.net/gh/nizigen/live2d_api@v1.4/"];let currentCdnIndex = 0;let loadSuccess = false;// 尝试加载资源的函数,支持本地优先+CDN回退async function tryLoadResources (cdnUrl) {try {// 优先尝试加载本地CSSlet cssPromise;try {cssPromise = loadExternalResource("/waifu.css", "css");console.log("尝试加载本地CSS: /waifu.css");} catch (localError) {console.warn("本地CSS加载失败,使用CDN:", localError);cssPromise = loadExternalResource(cdnUrl + "waifu.css", "css");}await Promise.all([cssPromise,loadExternalResource(cdnUrl + "waifu-tips.js", "js")]);loadSuccess = true;return cdnUrl;} catch (error) {console.warn(`CDN ${cdnUrl} 加载失败,尝试下一个...`);return null;}}// 尝试所有CDN直到成功或全部失败while (!loadSuccess && currentCdnIndex < cdnList.length) {const result = await tryLoadResources(cdnList[currentCdnIndex]);if (result) {console.log(`成功使用CDN: ${result}`);break;}currentCdnIndex++;}if (!loadSuccess) {console.error("所有CDN都无法访问,Live2D功能将被禁用");return;}const workingCdn = cdnList[currentCdnIndex];// 配置选项的具体用法见 README.mdtry {console.log("初始化Live2D Widget...");console.log("工作CDN:", workingCdn);console.log("waifu-tips.json路径:", workingCdn + "waifu-tips.json");initWidget({waifuPath: workingCdn + "waifu-tips.json",cdnPath: workingCdn,cubism2Path: live2d_path + "live2d.min.js",cubism5Path: "https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js",tools: ["hitokoto", "asteroids", "switch-model", "switch-texture", "photo", "info", "quit"],logLevel: "info", // 改为info级别以获得更多调试信息drag: false});console.log("Live2D Widget初始化完成");// 添加必要的CSS样式确保Live2D正确显示const style = document.createElement('style');style.textContent = `#waifu.waifu-active {bottom: 0 !important;transform: translateY(0) !important;}#waifu.waifu-hidden {bottom: -1000px !important;}#live2d {display: block !important;visibility: visible !important;pointer-events: auto !important;}#waifu-tool {display: block !important;opacity: 1 !important;pointer-events: auto !important;}#waifu-tips {display: block !important;pointer-events: auto !important;}`;document.head.appendChild(style);// 清除可能导致问题的缓存localStorage.removeItem("modelId");localStorage.removeItem("modelTexturesId");} catch (error) {console.error("Live2D初始化失败:", error);console.warn("建议检查网络连接或更换CDN源");}// 添加全局错误处理,防止Live2D错误导致页面崩溃window.addEventListener('error', (event) => {// 处理Live2D相关的错误if (event.message && event.message.includes('hitTest')) {console.warn("Live2D hitTest错误,已自动修复:", event.message);event.preventDefault(); // 阻止错误传播return false;}// 处理资源加载错误if (event.filename && (event.filename.includes('live2d') || event.filename.includes('textures.cache'))) {console.warn("Live2D资源加载错误,已自动处理:", event.message);event.preventDefault();return false;}});// 添加额外的检查和调试信息setTimeout(() => {const waifuElement = document.getElementById('waifu');const live2dCanvas = document.getElementById('live2d');console.log("Live2D元素检查:");console.log("- waifu元素存在:", !!waifuElement);console.log("- live2d画布存在:", !!live2dCanvas);if (waifuElement) {console.log("- waifu类名:", waifuElement.className);console.log("- waifu样式:", getComputedStyle(waifuElement).bottom);}if (live2dCanvas) {console.log("- 画布尺寸:", live2dCanvas.width, "x", live2dCanvas.height);console.log("- 画布样式:", getComputedStyle(live2dCanvas).display, getComputedStyle(live2dCanvas).visibility);}// 如果画布存在但不可见,强制显示if (live2dCanvas && getComputedStyle(live2dCanvas).display === 'none') {console.log("强制显示Live2D画布");live2dCanvas.style.display = 'block';live2dCanvas.style.visibility = 'visible';}// 如果waifu元素存在但没有active类,添加它if (waifuElement && !waifuElement.classList.contains('waifu-active')) {console.log("添加waifu-active类");waifuElement.classList.add('waifu-active');}// 如果Live2D加载失败,尝试降级方案if (!live2dCanvas || live2dCanvas.width === 0) {console.warn("Live2D模型加载失败,尝试降级显示");// 可以在这里添加降级的静态图片显示}}, 3000);
})();console.log(`く__,.ヘヽ. / ,ー、 〉\ ', !-─‐-i / /´/`ー' L//`ヽ、/ /, /| , , ',イ / /-‐/ i L_ ハ ヽ! iレ ヘ 7イ`ト レ'ァ-ト、!ハ| |!,/7 '0' ´0iソ| ||.从" _ ,,,, / |./ |レ'| i>.、,,__ _,.イ / .i |レ'| | / k_7_/レ'ヽ, ハ. || |/i 〈|/ i ,.ヘ | i |.|/ / i: ヘ! \ |kヽ>、ハ _,.ヘ、 /、!!'〈//`T´', \ `'7'ーr'レ'ヽL__|___i,___,ンレ|ノト-,/ |___./'ー' !_,.:
`);
-
启用Live2D:在
config/_default/params.yml
中设置(这就是Reimu专属的配置文件了,别的主题我不清楚,所以推荐大家都来用这个主题喵!):live2d_widgets:enable: trueposition: left # 或 right
-
修改autoload.js:原来的版本会有一个live2d-path,可以直接改成
https://fastly.jsdelivr.net/npm/live2d-widgets@1.0.0-rc.7/dist/
,我这里直接使用了多个cdn链接防止有的cdn链接无法访问,编辑themes/reimu/static/resources/live2d-widgets@0.9.0/autoload.js
,调整CDN列表以避免加载失败:const cdnList = ["https://cdn.jsdelivr.net/gh/nizigen/live2d_api@main/", // 稳定CDN优先"https://fastly.jsdelivr.net/gh/nizigen/live2d_api@main/","https://gcore.jsdelivr.net/gh/nizigen/live2d_api@main/","https://raw.githubusercontent.com/nizigen/live2d_api/main/" // 作为备用 ];
- 为什么这样?GitHub Raw不稳定(易400错误、限流),优先用jsDelivr代理,下面第三点对应的workingcdn其实就是通过cdnlist里面第一个能够使用的链接。还有就是加载Cubism3及以上的live2d模型需要core,我的尝试下
https://fastly.jsdelivr.net/npm/live2d-widgets@1.0.0-rc.7/dist/
,这个版本是可以找到对应的core的。
- 为什么这样?GitHub Raw不稳定(易400错误、限流),优先用jsDelivr代理,下面第三点对应的workingcdn其实就是通过cdnlist里面第一个能够使用的链接。还有就是加载Cubism3及以上的live2d模型需要core,我的尝试下
-
初始化:在autoload.js的initWidget函数配置的仓库:
详细的关于initWidget的参数的问题可以去原仓库看readme,这里不详细介绍了。大家在使用的时候把cdn列表里面的任意一个改为自己的用户名以及仓库名替换这里的workingcdn就好了,live2d_path建议就使用我在第二点提到的那个。
initWidget({waifuPath: workingCdn + "waifu-tips.json",cdnPath: workingCdn,cubism2Path: live2d_path + "live2d.min.js",cubism5Path: "https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js",tools: ["hitokoto", "asteroids", "switch-model", "switch-texture", "photo", "info", "quit"],logLevel: "info", // 改为info级别以获得更多调试信息drag: false});
3: 处理大文件(超过20MB的图片)
一些CDN(如jsDelivr)限制单文件20-50MB。如果有些纹理图片太大:
压缩:用WPS图片压缩或者在网上找压缩网站压缩到<20MB替换掉原图片。
通过以上步骤,应该可以在博客中用上自定义Live2D模型!如果遇到问题,欢迎在评论区提出来,如果是我遇到过的问题乐意给大家解答。效果预览:我的博客
(参考:Live2D官方文档、Reimu主题GitHub、live2d-widget、live2d-api)