领养一窝萌萌哒 Live2D 看板娘

作者: Timeless 更新时间: 阅读量: 623

之前在B站直播以及一些大佬的博客都看到了萌萌的看板娘,emmmmm不加上我睡不着啊,所以一顿摸索发现很多都用了live2d,顺藤摸瓜找到了一份教程

没错我也加上了,左下角萌萌哒~~~

参考大神的Demo:https://github.com/fghrsh/live2d_demo

Api接口:https://github.com/fghrsh/live2d_api

在 dalao 的 waifu-tips.js 的基础上,实现了套 API,加上了以下功能

  • 可以切换 Live2D 模型了

  • 支持 随机换装 和 顺序换装

以下内容需要根据 自身情况 进行修改,不能直接套用

waifu.css

.waifu {
    position: fixed;
    bottom: 0;
    left: 0;
    z-index: 1;
    font-size: 0;
    transition: all .3s ease-in-out;
    -webkit-transform: translateY(3px);
    transform: translateY(3px);
}
.waifu:hover {
    -webkit-transform: translateY(0);
    transform: translateY(0);
}
@media (max-width:768px) {
    .waifu {
        display: none;
    }
}
.waifu-tips {
    opacity: 0;
    width: 250px;
    height: 70px;
    margin: -20px 20px;
    padding: 5px 10px;
    border: 1px solid #009fd9;
    border-radius: 12px;
    background-color: rgba(255, 255, 255, 0.8);
    box-shadow: 0 1px 3px #66ccff;
    font-size: 12px;
    text-overflow: ellipsis;
    overflow: hidden;
    position: absolute;
    animation-delay: 5s;
    animation-duration: 50s;
    animation-iteration-count: infinite;
    animation-name: shake;
    animation-timing-function: ease-in-out;
}
.waifu-tool {
    display: none;
    color: #aaa;
    top: 50px;
    right: 10px;
    font-size: 14px;
    position: absolute;
}
.waifu:hover .waifu-tool {
    display: block;
}
.waifu-tool span {
    display: block;
    cursor: pointer;
    color: #5b6c7d;
    line-height: 20px;
    transition: 0.2s;
}
.waifu-tool span:hover {
    color: #34495e;
}
.waifu #live2d {
    position: relative;
}
@keyframes shake {
    2% {
        transform: translate(0.5px, -1.5px) rotate(-0.5deg);
    }
    4% {
        transform: translate(0.5px, 1.5px) rotate(1.5deg);
    }
    6% {
        transform: translate(1.5px, 1.5px) rotate(1.5deg);
    }
    8% {
        transform: translate(2.5px, 1.5px) rotate(0.5deg);
    }
    10% {
        transform: translate(0.5px, 2.5px) rotate(0.5deg);
    }
    12% {
        transform: translate(1.5px, 1.5px) rotate(0.5deg);
    }
    14% {
        transform: translate(0.5px, 0.5px) rotate(0.5deg);
    }
    16% {
        transform: translate(-1.5px, -0.5px) rotate(1.5deg);
    }
    18% {
        transform: translate(0.5px, 0.5px) rotate(1.5deg);
    }
    20% {
        transform: translate(2.5px, 2.5px) rotate(1.5deg);
    }
    22% {
        transform: translate(0.5px, -1.5px) rotate(1.5deg);
    }
    24% {
        transform: translate(-1.5px, 1.5px) rotate(-0.5deg);
    }
    26% {
        transform: translate(1.5px, 0.5px) rotate(1.5deg);
    }
    28% {
        transform: translate(-0.5px, -0.5px) rotate(-0.5deg);
    }
    30% {
        transform: translate(1.5px, -0.5px) rotate(-0.5deg);
    }
    32% {
        transform: translate(2.5px, -1.5px) rotate(1.5deg);
    }
    34% {
        transform: translate(2.5px, 2.5px) rotate(-0.5deg);
    }
    36% {
        transform: translate(0.5px, -1.5px) rotate(0.5deg);
    }
    38% {
        transform: translate(2.5px, -0.5px) rotate(-0.5deg);
    }
    40% {
        transform: translate(-0.5px, 2.5px) rotate(0.5deg);
    }
    42% {
        transform: translate(-1.5px, 2.5px) rotate(0.5deg);
    }
    44% {
        transform: translate(-1.5px, 1.5px) rotate(0.5deg);
    }
    46% {
        transform: translate(1.5px, -0.5px) rotate(-0.5deg);
    }
    48% {
        transform: translate(2.5px, -0.5px) rotate(0.5deg);
    }
    50% {
        transform: translate(-1.5px, 1.5px) rotate(0.5deg);
    }
    52% {
        transform: translate(-0.5px, 1.5px) rotate(0.5deg);
    }
    54% {
        transform: translate(-1.5px, 1.5px) rotate(0.5deg);
    }
    56% {
        transform: translate(0.5px, 2.5px) rotate(1.5deg);
    }
    58% {
        transform: translate(2.5px, 2.5px) rotate(0.5deg);
    }
    60% {
        transform: translate(2.5px, -1.5px) rotate(1.5deg);
    }
    62% {
        transform: translate(-1.5px, 0.5px) rotate(1.5deg);
    }
    64% {
        transform: translate(-1.5px, 1.5px) rotate(1.5deg);
    }
    66% {
        transform: translate(0.5px, 2.5px) rotate(1.5deg);
    }
    68% {
        transform: translate(2.5px, -1.5px) rotate(1.5deg);
    }
    70% {
        transform: translate(2.5px, 2.5px) rotate(0.5deg);
    }
    72% {
        transform: translate(-0.5px, -1.5px) rotate(1.5deg);
    }
    74% {
        transform: translate(-1.5px, 2.5px) rotate(1.5deg);
    }
    76% {
        transform: translate(-1.5px, 2.5px) rotate(1.5deg);
    }
    78% {
        transform: translate(-1.5px, 2.5px) rotate(0.5deg);
    }
    80% {
        transform: translate(-1.5px, 0.5px) rotate(-0.5deg);
    }
    82% {
        transform: translate(-1.5px, 0.5px) rotate(-0.5deg);
    }
    84% {
        transform: translate(-0.5px, 0.5px) rotate(1.5deg);
    }
    86% {
        transform: translate(2.5px, 1.5px) rotate(0.5deg);
    }
    88% {
        transform: translate(-1.5px, 0.5px) rotate(1.5deg);
    }
    90% {
        transform: translate(-1.5px, -0.5px) rotate(-0.5deg);
    }
    92% {
        transform: translate(-1.5px, -1.5px) rotate(1.5deg);
    }
    94% {
        transform: translate(0.5px, 0.5px) rotate(-0.5deg);
    }
    96% {
        transform: translate(2.5px, -0.5px) rotate(-0.5deg);
    }
    98% {
        transform: translate(-1.5px, -1.5px) rotate(-0.5deg);
    }
    0%, 100% {
        transform: translate(0, 0) rotate(0);
    }
}
@font-face {
    font-family: 'Flat-UI-Icons';
    src: url('flat-ui-icons-regular.eot');
    src: url('flat-ui-icons-regular.eot?#iefix') format('embedded-opentype'), url('flat-ui-icons-regular.woff') format('woff'), url('flat-ui-icons-regular.ttf') format('truetype'), url('flat-ui-icons-regular.svg#flat-ui-icons-regular') format('svg');
}
[class^="fui-"], [class*="fui-"] {
    font-family: 'Flat-UI-Icons';
    speak: none;
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
.fui-cross:before {
    content: "\e609";
}
.fui-info-circle:before {
    content: "\e60f";
}
.fui-photo:before {
    content: "\e62a";
}
.fui-eye:before {
    content: "\e62c";
}
.fui-chat:before {
    content: "\e62d";
}
.fui-home:before {
    content: "\e62e";
}
.fui-user:before {
    content: "\e631";
}

waifu-tips.js

String.prototype.render = function (context) {
	var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;
	return this.replace(tokenReg, function (word, slash1, token, slash2) {
		if (slash1 || slash2) {  
			return word.replace('\\', '');
		}
		var variables = token.replace(/\s/g, '').split('.');
		var currentObject = context;
		var i, length, variable;
		for (i = 0, length = variables.length; i < length; ++i) {
			variable = variables[i];
			currentObject = currentObject[variable];
			if (currentObject === undefined || currentObject === null) return '';
		}
		return currentObject;
	});
};
/**
 * Wife
 * apiurl {string} 模型后端接口
 * modelId {number} 模型 ID
 * modelTexturesId {number} 材质 ID
 */
var Wife ={
	apiurl: "//cos.timelessq.com/live2d/",
	modelId: 3, // 模型 ID
	modelTexturesId: 82, // 材质 ID
	drag: function(obj){
		var ele = $(obj);
		ele.bind('mousedown', start);
		function start(e) {
			var ol = ele.offset().left-$(document).scrollLeft();
			var ot = ele.offset().top-$(document).scrollTop();
			deltaX = e.pageX - ol;
			deltaY = e.pageY - ot;
			$(document).bind({
				'mousemove': move,
				'mouseup': stop
			});
			return false;
		}
		function move(e) {
			var x=e.pageX - deltaX;
			var y=e.pageY - deltaY;
			if(x < 0) {
				x = 0;
			} else if(x > $(window).width() - ele.outerWidth(true)) {
				x = $(window).width() - ele.outerWidth(true);
			}
			if(y < 0) {
				y = 0;
			} else if(y > $(window).height() - ele.outerHeight(true)) {
				y = $(window).height() - ele.outerHeight(true);
			}
			ele.css({
				"left": x,
				"top": y
			});
			return false;
		}
		function stop() {
			$(document).unbind({
				'mousemove': move,
				'mouseup': stop
			});
		}
	},
	/** 提示框 */
	showMessage: function(text, timeout, flag){
		if(flag || sessionStorage.getItem('waifu-text') === '' || sessionStorage.getItem('waifu-text') === null){
			if(Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1)-1];
			if(flag) sessionStorage.setItem('waifu-text', text);
			$('.waifu-tips').stop();
			$('.waifu-tips').html(text).fadeTo(200, 1);
			if (timeout === undefined) timeout = 3000;
			Wife.hideMessage(timeout);
		}
	},
	hideMessage: function(timeout){
		$('.waifu-tips').stop().css('opacity',1);
		if (timeout === undefined) timeout = 3000;
		window.setTimeout(function() {sessionStorage.removeItem('waifu-text')}, timeout);
		$('.waifu-tips').delay(timeout).fadeTo(200, 0);
	},
	/** 初始化模型 */
	initModel: function(waifuPath){
		if (waifuPath === undefined) waifuPath = '';
		var modelId = localStorage.getItem('modelId');
		var modelTexturesId = localStorage.getItem('modelTexturesId');
		if (modelId == null) {      
			/* 首次访问加载 指定模型 的 指定材质 */
			var modelId = Wife.modelId;            
			var modelTexturesId = Wife.modelTexturesId;   
		} 
		Wife.loadModel(modelId, modelTexturesId);
		Wife.welcome();
		Wife.interaction();
	},
	loadModel: function(modelId, modelTexturesId){
		localStorage.setItem('modelId', modelId);
		if (modelTexturesId === undefined) modelTexturesId = 0;
		localStorage.setItem('modelTexturesId', modelTexturesId);
		loadlive2d(
			'live2d', Wife.apiurl+'get/?id='+modelId+'-'+modelTexturesId,
			console.log('live2d','模型 '+modelId+'-'+modelTexturesId+' 加载完成')
		);
	},
	/** 更换模型*/
	loadRandModel: function(){
		var modelId = localStorage.getItem('modelId');
		var modelTexturesId = localStorage.getItem('modelTexturesId');
		var modelTexturesRandMode = 'rand';     // 可选 'rand'(随机), 'switch'(顺序) 
		$.ajax({
			cache: false,
			url: Wife.apiurl+modelTexturesRandMode+'_textures/?id='+modelId+'-'+modelTexturesId,
			dataType: "json",
			success: function (result){
				if (result.textures['id'] == 1 && (modelTexturesId == 1 || modelTexturesId == 0)) {
					Wife.showMessage('我还没有其他衣服呢', 3000, true);
				} else {
					Wife.showMessage('我的新衣服好看嘛', 3000, true);
				}
				Wife.loadModel(modelId, result.textures['id']);
			}
		});
	},
	loadOtherModel: function(){
		var modelId = localStorage.getItem('modelId');
		var modelTexturesRandMode = 'switch';     // 可选 'rand'(随机), 'switch'(顺序)
		$.ajax({
			cache: false,
			url: Wife.apiurl+modelTexturesRandMode+'/?id='+modelId,
			dataType: "json",
			success: function (result){
				Wife.loadModel(result.model['id']);
				Wife.showMessage(result.model['message'], 3000, true);
			}
		});
	},
	welcome: function(){
		var text;
		var SiteIndexUrl = window.location.protocol+'//'+window.location.hostname+'/';  // 自动获取主页 
		if (window.location.href == SiteIndexUrl) {      // 如果是主页
			var now = (new Date()).getHours();
			if (now > 23 || now <= 5) {
				text = '你是夜猫子呀?这么晚还不睡觉,明天起的来嘛';
			} else if (now > 5 && now <= 7) {
				text = '早上好!一日之计在于晨,美好的一天就要开始了';
			} else if (now > 7 && now <= 11) {
				text = '上午好!工作顺利嘛,不要久坐,多起来走动走动哦!';
			} else if (now > 11 && now <= 14) {
				text = '中午了,工作了一个上午,现在是午餐时间!';
			} else if (now > 14 && now <= 17) {
				text = '午后很容易犯困呢,今天的运动目标完成了吗?';
			} else if (now > 17 && now <= 19) {
				text = '傍晚了!窗外夕阳的景色很美丽呢,最美不过夕阳红~';
			} else if (now > 19 && now <= 21) {
				text = '晚上好,今天过得怎么样?';
			} else if (now > 21 && now <= 23) {
				text = '已经这么晚了呀,早点休息吧,晚安~';
			} else {
				text = '嗨~ 快来逗我玩吧!';
			}
		} else {
			if(document.referrer !== ''){
				var referrer = document.createElement('a');
				referrer.href = document.referrer;
				var domain = referrer.hostname.split('.')[1];
				if (window.location.hostname == referrer.hostname) {
					text = '欢迎阅读<span style="color:#0099cc;">『' + document.title.split(' - ')[0] + '』</span>';
				} else if (domain == 'baidu') {
					text = 'Hello! 来自 百度搜索 的朋友<br>你是搜索 <span style="color:#0099cc;">' + referrer.search.split('&wd=')[1].split('&')[0] + '</span> 找到的我吗?';
				} else if (domain == 'so') {
					text = 'Hello! 来自 360搜索 的朋友<br>你是搜索 <span style="color:#0099cc;">' + referrer.search.split('&q=')[1].split('&')[0] + '</span> 找到的我吗?';
				} else if (domain == 'google') {
					text = 'Hello! 来自 谷歌搜索 的朋友<br>欢迎阅读<span style="color:#0099cc;">『' + document.title.split(' - ')[0] + '』</span>';
				} else {
					text = 'Hello! 来自 <span style="color:#0099cc;">' + referrer.hostname + '</span> 的朋友';
				}
			} else {
				text = '欢迎阅读<span style="color:#0099cc;">『' + document.title.split(' - ')[0] + '』</span>';
			}
		}
		Wife.showMessage(text, 3000);
	},
	/** 交互时间 */
	interaction: function(){
		$.each(Wife.texts.mouseover, function (index, tips){
			$(document).on("mouseover", tips.selector, function (){
				var text = tips.text;
				if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1];
				text = text.render({text: $(this).text()});
				Wife.showMessage(text, 3000);
			});
		});
		$.each(Wife.texts.click, function (index, tips){
			$(document).on("click", tips.selector, function (){
				var text = tips.text;
				if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1];
				text = text.render({text: $(this).text()});
				Wife.showMessage(text, 3000, true);
			});
		});
		if(Wife.texts.seasons!=undefined){
			$.each(Wife.texts.seasons, function (index, tips){
				var now = new Date();
				var after = tips.date.split('-')[0];
				var before = tips.date.split('-')[1] || after;
				
				if((after.split('/')[0] <= now.getMonth()+1 && now.getMonth()+1 <= before.split('/')[0]) && 
				   (after.split('/')[1] <= now.getDate() && now.getDate() <= before.split('/')[1])){
					var text = tips.text;
					if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1];
					text = text.render({year: now.getFullYear()});
					Wife.showMessage(text, 6000, true);
				}
			});
		}
	},
	texts: {
		"mouseover": [
			{
				"selector": ".waifu-tool .fui-home",
				"text": ["点击前往首页,想回到上一页可以使用浏览器的后退功能哦"]
			},
			{
				"selector": ".waifu-tool .fui-chat",
				"text": ["啊呀 我哪里做错了 你要去投诉我么~"]
			},
			{
				"selector": ".waifu-tool .fui-eye",
				"text": ["┭┮﹏┭┮ 你不喜欢人家了么,那让我的好朋友见见你"]
			},
			{
				"selector": ".waifu-tool .fui-user",
				"text": ["つ﹏⊂ 居然要人家换衣服呢"]
			},
			{
				"selector": ".waifu-tool .fui-photo",
				"text": ["123茄子 西瓜甜不甜,宝宝萌不萌"]
			},
			{
				"selector": ".waifu-tool .fui-cross",
				"text": ["(个_个) 真的到了要分开的时候了么"]
			},
	        {
	            "selector": ".layui-container a[href^='http'],.layui-container a[href^='https'],.header-nav a",
	            "text": ["要看看 <span style=\"color:#0099cc;\">{text}</span> 么?"]
	        },
			{
	            "selector": ".wallpaper .wallpaper-info a,.i-blog h2 a,.l-blog h2 a,.sidebar a,.l-download h2 a",
	            "text": ["要看看 <span style=\"color:#0099cc;\">{text}</span> 么?"]
	        },
			{
	            "selector": ".header-top-logo",
	            "text": ["这是Logo,点击可以前往首页哟"]
	        },
			{
	            "selector": ".application a",
	            "text": ["<span style=\"color:#0099cc;\">{text}</span> 哇咔咔 介个好玩的哟"]
	        },
			{
	            "selector": ".l-wallpaper .picture-item a",
	            "text": ["这是 <span style=\"color:#0099cc;\">{text}</span> 的壁纸ヾ(≧O≦)〃嗷~"]
	        },
			{
	            "selector": ".resource .download-link",
	            "text": ["下载 <span style=\"color:#0099cc;\">{text}</span> 要小钱钱的哟"]
	        },
			{
	            "selector": ".l-video .play-see",
	            "text": ["亲 <span style=\"color:#0099cc;\">{text}</span> 炒鸡好看的 喵"]
	        },
			{
	            "selector": ".about li:eq(0)",
	            "text": ["嘻嘻 介个是主人的说明书~"]
	        },
			{
	            "selector": ".about li:eq(1)",
	            "text": ["嘿嘿 介个是主人的白日梦~"]
	        },
			{
	            "selector": ".about li:eq(2)",
	            "text": ["嚯嚯 介个是我(^U^)ノ~YO 好看不"]
	        },
			{
	            "selector": ".about li:eq(3)",
	            "text": ["诶诶 介个介个? 嗯对 这个是罗小黑"]
	        },
	        {
	            "selector": ".sub-nav a",
	            "text": ["翻页比较麻烦吗,点击可以显示这篇文章的目录呢"]
	        },
	        {
	            "selector": ".layui-fixbar li:eq(0)",
	            "text": ["想要去反馈些什么吗?"]
	        },
	        {
	            "selector": ".layui-fixbar li:eq(1)",
	            "text": ["小姐姐? QQ了解一下"]
	        },
	        {
	            "selector": ".qr-code img",
	            "text": ["手机扫一下就能继续看,很方便呢"]
	        },
	        {
	            "selector": ".comment_reply",
	            "text": ["要吐槽些什么呢"]
	        },
	        {
	            "selector": ".layui-fixbar-top",
	            "text": ["回到开始的地方吧"]
	        },
	        {
	            "selector": ".f-submit",
	            "text": ["要[提交]^(Commit)了吗,首次留言需要审核,请耐心等待~"]
	        },
	        {
	            "selector": ".blog-content img",
	            "text": ["点击图片可以放大的呢"]
	        },
	        {
	            "selector": ".s-search .s-inp",
	            "text": ["找不到想看的内容?搜索看看吧"]
	        },
	        {
	            "selector": ".prev-page",
	            "text": ["去上一页看看吧"]
	        },
	        {
	            "selector": ".next-page",
	            "text": ["去下一页看看吧"]
	        },
	        {
	            "selector": "c-player div.lyric-button",
	            "text": ["有<span style=\"color:#0099cc;\">歌词</span>的话就能跟着一起唱呢"]
	        },
	        {
	            "selector": ".waifu #live2d",
	            "text": ["干嘛呢你,快把手拿开", "鼠…鼠标放错地方了!", "我生气了啊~~( ﹁ ﹁ ) ~~~ 哄不回来的那种", " 怕帕(๑⁼̴̀д⁼̴́๑)"]
	        }
	    ],
	    "click": [
	        {
	            "selector": ".waifu #live2d",
	            "text": [
	            	"是…是不小心碰到了吧", 
	            	"萝莉控是什么呀", 
	            	"喵喵喵(๑•́ ∀ •̀๑)", 
	            	"๑乛◡乛๑嘿嘿", 
	            	"你看到我的小熊了吗", 
	            	"再摸的话我可要报警了!⌇●﹏●⌇", 
	            	"妖妖零吗,这里有个家伙一直在摸我(ó﹏ò。)"
	            ]
	        }
	    ]
	}
};
//初始化
(function(){
    Wife.initModel(Wife.apiurl);
    Wife.drag(".waifu");
    /** 开启开发者工具 */
    var re = /x/;
    re.toString = function() {
    	Wife.showMessage('哈哈,你打开了控制台,是想要看看我的秘密吗?', 3000, true);
    	return '';
    };
    /** 复制 */
    $(document).on('copy', function (){
    	Wife.showMessage('你都复制了些什么呀,转载要记得加上出处哦', 3000, true);
    });
    /** UI组件 */
    $('.waifu-tool .fui-home').click(function (){
    	window.location = window.location.protocol+'//'+window.location.hostname+'/'
    });
    $('.waifu-tool .fui-eye').click(function (){
    	Wife.loadOtherModel();
    });
    $('.waifu-tool .fui-user').click(function (){
    	Wife.loadRandModel();
    });
    $('.waifu-tool .fui-chat').click(function (){
    	//可以接 图灵机器人 的接口哈
    });
    $('.waifu-tool .fui-cross').click(function (){
    	sessionStorage.setItem('waifu-dsiplay', 'none');
    	Wife.showMessage('愿你有一天能与重要的人重逢', 1300, true);
    	window.setTimeout(function() {$('.waifu').hide();}, 1300);
    });
    $('.waifu-tool .fui-photo').click(function (){
    	Wife.showMessage('照好了嘛,是不是很可爱呢?', 3000, true);
    	window.Live2D.captureName = 'Pio.png';
    	window.Live2D.captureFrame = true;
    });    
})();

html结构

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<link rel="stylesheet" type="text/css" href="https://www.example.com/lib/waifu/waifu.css"/>
	</head>
	<body>
		<div class="waifu">
		    <!-- 提示框 -->
		    <div class="waifu-tips"></div>
		    <!-- 看板娘画布 -->
		    <canvas id="live2d" width="280" height="250" class="live2d"></canvas>
		    <!-- 工具栏 -->
		    <div class="waifu-tool">
		        <span class="fui-home"></span>
		        <span class="fui-chat"></span>
		        <span class="fui-eye"></span>
		        <span class="fui-user"></span>
		        <span class="fui-photo"></span>
		        <span class="fui-info-circle"></span>
		        <span class="fui-cross"></span>
		    </div>
		</div>
		<script src="lib/waifu/live2d.js"></script>
		<script src="lib/waifu/waifu-tips.js"></script>
	</body>
</html>

想知道Api接口的模型列表么

搬好小板凳,我们走:https://www.timelessq.com/webapp/24-cn.html