Avatar.html
<!DOCTYPE html>
<html lang="ja"><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$(e=>{
var a = new Avatar(30).addSprite(
new Avatar.Sprite('100.png',0,100,150,150),
new Avatar.Sprite('200.png',0,0,400,400),
new Avatar.Pachi('300.png',100,50,200,100),
new Avatar.Paku('400.png',100,100,200,100),
new Avatar.Sprite('500.png',200,50,200,200),
)
$('body').append(a.view)
})
class Avatar{
constructor(...a){
this.view = $('<div class="Avatar">').append(
this.$content = $('<div class="content">')
).click(e=>{
this.view.off("click")
this.voice = new Avatar.Voice()
setInterval(e=>{
this.loop.forEach(v=>{v.loop(this)})
this.frame++
},Math.round(1000/a[0]))
})
this.frame = 0
this.loop = []
}
addSprite(...a){
this.sprite = a
a.forEach(v=>{
this.$content.append(v.view)
if(v.loop) this.loop.push(v)
})
return this
}
}
Avatar.Voice = class{
constructor(...a){
this.volume = 0
navigator.mediaDevices.getUserMedia({audio:true}).then(stream=>{
var c = new AudioContext();
var s = c.createMediaStreamSource(stream)
var p = c.createScriptProcessor(a[0]||1024)
p.onaudioprocess = e=>{
var d = e.inputBuffer.getChannelData(0)
this.volume = d.reduce((a,b)=>a+Math.abs(b),0)/p.bufferSize
}
p.connect(c.destination)
s.connect(p)
})
}
speaking(){
return this.volume>0.01
}
}
Avatar.Sprite = class{
constructor(...a){
var s = a[0].split('.').shift()
this.view = $('<div>').addClass('Sprite s'+s).css({
backgroundImage:`url(${a[0]})`,
left:a[1],top:a[2],width:a[3],height:a[4]
})
this.X = a[1]
this.y = a[2]
this.delta = Math.floor(60*Math.random())
this.init(...a)
}
init(...a){
var img = new Image()
img.src = a[0]
this.frame = []
img.onload = e=>{
var j = Math.floor(img.height/a[4])
for(var i=0;i<j;i++){
this.frame.push(-i*a[4])
}
}
}
loop(e){
this.top(e)
if(this.frame.length==0) return
if(e.frame%5>0) return
if(e.voice.speaking()) this.random()
}
top(e){
this.view.css({top:this.y+Math.sin((e.frame+this.delta)/30)*3})
}
random(){
var i = Math.floor(Math.random()*this.frame.length)
this.view.css({backgroundPositionY:this.frame[i]})
}
}
Avatar.Pachi = class extends Avatar.Sprite{
init(){
this.blink = '000121000000122222100000000000000000000000000000000000000000000'
}
loop(e){
this.top(e)
var f = e.frame%this.blink.length
this.view.css({backgroundPositionY:-this.blink[f]*100})
}
}
Avatar.Paku = class extends Avatar.Sprite{
loop(e){
this.top(e)
if(this.frame.length==0) return
if(e.frame%3>0) return
if(e.voice.speaking()) {
this.random()
}else{
this.view.css({backgroundPositionY:0})
}
}
}
</script>
<style>
*{margin:0;padding:0;}
body{background:#0F0;}
.Sprite{position:absolute;}
.Avatar{
position:fixed;right:0;bottom:0;width:400px;height:400px;
animation:horizontal 1.6s ease-in-out infinite alternate;
}
.Avatar .content{
animation:vertical 1.3s ease-in-out infinite alternate;
}
@keyframes horizontal {
0% { transform:translateX( -3px); }
100% { transform:translateX( 0px); }
}
@keyframes vertical {
0% { transform:translateY(-6px); }
100% { transform:translateY( 0px); }
}
</style>
</head>
</html>