[auto] unlink animeCat Kopie.js
This commit is contained in:
@@ -1,158 +0,0 @@
|
|||||||
// animeCat.js
|
|
||||||
export class AnimeCat {
|
|
||||||
/**
|
|
||||||
* @param {HTMLElement} container
|
|
||||||
* @param {Object} [options]
|
|
||||||
*/
|
|
||||||
constructor(container, options = {}) {
|
|
||||||
this.container = container;
|
|
||||||
this.images = Object.assign({
|
|
||||||
default: 'default.png',
|
|
||||||
eyesClosed: 'eyes_closed.png',
|
|
||||||
mouthOpen: 'mouth_open.png'
|
|
||||||
}, options.images);
|
|
||||||
this.blinkMin = options.blinkMin ?? 5000;
|
|
||||||
this.blinkMax = options.blinkMax ?? 15000;
|
|
||||||
this.blinkDuration = options.blinkDuration?? 175;
|
|
||||||
this.talkInterval = options.talkInterval ?? 300;
|
|
||||||
|
|
||||||
this._isSpeaking = false;
|
|
||||||
this._blinkTimeout = null;
|
|
||||||
this._talkIntervalId = null;
|
|
||||||
this._speechTimeout = null;
|
|
||||||
this._mouthOpen = false;
|
|
||||||
|
|
||||||
this._createElements();
|
|
||||||
this._bindMouseHold();
|
|
||||||
this._startBlinking();
|
|
||||||
}
|
|
||||||
|
|
||||||
_createElements() {
|
|
||||||
this.wrapper = document.createElement('div');
|
|
||||||
this.wrapper.style.position = 'relative';
|
|
||||||
this.wrapper.style.display = 'inline-block';
|
|
||||||
|
|
||||||
// cat image
|
|
||||||
this.img = document.createElement('img');
|
|
||||||
this.img.src = this.images.default;
|
|
||||||
// disable drag & selection
|
|
||||||
this.img.draggable = false;
|
|
||||||
this.img.style.userSelect = 'none';
|
|
||||||
this.img.style.webkitUserSelect = 'none';
|
|
||||||
this.img.style.MozUserSelect = 'none';
|
|
||||||
this.img.style.msUserSelect = 'none';
|
|
||||||
// some browsers need this to stop the default drag ghost
|
|
||||||
this.img.style.webkitUserDrag = 'none';
|
|
||||||
|
|
||||||
this.wrapper.appendChild(this.img);
|
|
||||||
|
|
||||||
// speech bubble
|
|
||||||
this.bubble = document.createElement('div');
|
|
||||||
Object.assign(this.bubble.style, {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '100%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
padding: '8px 12px',
|
|
||||||
background: 'white',
|
|
||||||
border: '1px solid #ccc',
|
|
||||||
borderRadius: '4px',
|
|
||||||
boxShadow: '0 2px 6px rgba(0,0,0,0.2)',
|
|
||||||
opacity: '0',
|
|
||||||
transition: 'opacity 0.3s',
|
|
||||||
maxWidth: '200px',
|
|
||||||
wordWrap: 'break-word',
|
|
||||||
fontFamily: 'sans-serif',
|
|
||||||
fontSize: '14px',
|
|
||||||
color: '#333',
|
|
||||||
pointerEvents: 'none'
|
|
||||||
});
|
|
||||||
this.wrapper.appendChild(this.bubble);
|
|
||||||
|
|
||||||
this.container.appendChild(this.wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
_startBlinking() {
|
|
||||||
const delay = this.blinkMin + Math.random() * (this.blinkMax - this.blinkMin);
|
|
||||||
this._blinkTimeout = setTimeout(() => {
|
|
||||||
if (!this._isSpeaking) {
|
|
||||||
this.img.src = this.images.eyesClosed;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.img.src = this.images.default;
|
|
||||||
this._startBlinking();
|
|
||||||
}, this.blinkDuration);
|
|
||||||
} else {
|
|
||||||
this._startBlinking();
|
|
||||||
}
|
|
||||||
}, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
_bindMouseHold() {
|
|
||||||
let holdTimer = null;
|
|
||||||
|
|
||||||
const closeEyes = () => {
|
|
||||||
clearTimeout(holdTimer);
|
|
||||||
this.img.src = this.images.eyesClosed;
|
|
||||||
};
|
|
||||||
const reopenEyes = () => {
|
|
||||||
clearTimeout(holdTimer);
|
|
||||||
if (!this._isSpeaking) {
|
|
||||||
this.img.src = this.images.default;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.img.addEventListener('mousedown', () => {
|
|
||||||
// if currently talking, ignore hold-to-close
|
|
||||||
if (this._isSpeaking) return;
|
|
||||||
closeEyes();
|
|
||||||
// force reopen after max 5s
|
|
||||||
holdTimer = setTimeout(reopenEyes, 4000);
|
|
||||||
});
|
|
||||||
|
|
||||||
['mouseup', 'mouseleave'].forEach(evt =>
|
|
||||||
this.img.addEventListener(evt, reopenEyes)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Call when streaming text begins */
|
|
||||||
beginSpeech() {
|
|
||||||
clearTimeout(this._speechTimeout);
|
|
||||||
clearInterval(this._talkIntervalId);
|
|
||||||
|
|
||||||
this._isSpeaking = true;
|
|
||||||
this._mouthOpen = false;
|
|
||||||
this.img.src = this.images.default;
|
|
||||||
this.bubble.style.opacity = '1';
|
|
||||||
this.bubble.textContent = '';
|
|
||||||
|
|
||||||
this._talkIntervalId = setInterval(() => {
|
|
||||||
this._mouthOpen = !this._mouthOpen;
|
|
||||||
this.img.src = this._mouthOpen
|
|
||||||
? this.images.mouthOpen
|
|
||||||
: this.images.default;
|
|
||||||
}, this.talkInterval / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Append a chunk of streamed text */
|
|
||||||
appendSpeech(chunk) {
|
|
||||||
this.bubble.textContent += chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Call when the stream ends */
|
|
||||||
endSpeech() {
|
|
||||||
clearInterval(this._talkIntervalId);
|
|
||||||
this.img.src = this.images.default;
|
|
||||||
this._speechTimeout = setTimeout(() => {
|
|
||||||
this.bubble.style.opacity = '0';
|
|
||||||
this._isSpeaking = false;
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clean up timers & DOM */
|
|
||||||
destroy() {
|
|
||||||
clearTimeout(this._blinkTimeout);
|
|
||||||
clearInterval(this._talkIntervalId);
|
|
||||||
clearTimeout(this._speechTimeout);
|
|
||||||
this.wrapper.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user