Merge pull request #1 from eugene-serb/dev

Dev
This commit is contained in:
Eugene Serb 2022-06-15 19:06:15 +03:00 committed by GitHub
commit dfca84d039
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 684 additions and 2 deletions

72
404.md Normal file
View File

@ -0,0 +1,72 @@
<html lang="en-us" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<link rel="shortcut icon" type="image/x-icon" href="https://eugene-serb.github.io/img/favicon.ico" />
<link rel="stylesheet" type="text/css" href="https://eugene-serb.github.io/css/styles.css" />
<style type="text/css">
div.markdown-body > h1, div.footer {
display: none;
}
@media only screen and (min-width: 360px) {
.px-3 {
padding-right: 0 !important;
padding-left: 0 !important;
}
.my-5 {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
}
</style>
<meta name="google-site-verification" content="qLQbgnmQEfvprDF8WR6oL_b_Qt0R9kKcIEOfHqWlFm8" />
<meta name="yandex-verification" content="e6e0bff7caaa7ecd" />
<meta name="msvalidate.01" content="6E1771734F083E5366205F06314C3577" />
<meta name='wmail-verification' content='46d069b79f9c774ce0bbf55f46aef201' />
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-4NB4LGNNLB"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-4NB4LGNNLB');
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<!-- Yandex.Metrika counter -->
<script type="text/javascript">
(function (m, e, t, r, i, k, a) {
m[i] = m[i] || function () { (m[i].a = m[i].a || []).push(arguments) };
m[i].l = 1 * new Date(); k = e.createElement(t), a = e.getElementsByTagName(t)[0], k.async = 1, k.src = r, a.parentNode.insertBefore(k, a)
})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(79722217, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
</script>
<!-- /Yandex.Metrika counter -->
</head>
<body>
<section class="banner-container">
<div class="banner">
<h1>404</h1>
<span>File not found. Please, go to the </span><a href="https://eugene-serb.github.io/" target="_self">homepage</a><br />
<span>Contact info: </span><a href="https://twitter.com/eugene_serb/" target="_blank">@eugene_serb</a>
</div>
</section>
<script src="https://eugene-serb.github.io/js/scripts.js"></script>
<noscript><div><img src="https://mc.yandex.ru/watch/76610724" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
</body>
</html>

View File

View File

@ -1,2 +1,9 @@
# vibration-master
Vibration Master in Javascript, HTML and CSS
# Vibration Master
Vibration Master in Javascript, HTML and CSS.
This is a browser vibration master that can make a vibration massager for your arms from your gamepad.
If you are interested in this or my other projects, or would like to suggest and share ideas with me, or just talk to me, contact me: *[@eugene_serb](https://t.me/eugene_serb)*
Follow me on Twitter: *[@eugene_serb](https://twitter.com/eugene_serb)*

77
css/vibration-master.css Normal file
View File

@ -0,0 +1,77 @@
/* -------------- */
/* GAMEPAD MASTER */
/* -------------- */
@charset 'UTF-8';
.vibration-master {
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 16px;
}
.message-box {
text-align: center;
}
.device-box {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 16px;
text-align: center;
}
.device-list {
display: flex;
flex-direction: row;
justify-content: space-around;
gap: 16px;
}
.list-item {
max-width: 100%;
padding: 16px;
border: 4px solid var(--color-background);
outline: 2px solid var(--color-border-alpha);
border-radius: 4px;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 16px;
}
@media only screen and (min-width: 640px) {
.list-item {
max-width: 50%;
}
}
.list-item_selected {
outline: 2px solid var(--color-border);
}
.list-item__info {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.list-item__info > div {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.controls-banner {
width: 100%;
text-align: center;
}
.controls-banner__list {
display: flex;
flex-direction: column;
justify-content: space-between;
}

134
index.html Normal file
View File

@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en-us" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Eugene Serb Vibration Master</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<meta name="robots" content="all" />
<link rel="canonical" href="https://eugene-serb.github.io/vibration-master/" />
<link rel="shortcut icon" type="image/x-icon" href="https://eugene-serb.github.io/img/favicon.ico" />
<link rel="stylesheet" type="text/css" href="https://eugene-serb.github.io/css/styles.css" />
<link rel="stylesheet" type="text/css" href="css/vibration-master.css" />
<meta name="author" content="Eugene Serb" />
<meta name="copyright" content="Eugene Serb, 2021 2022" />
<meta name="publisher-email" content="eugene.serb@gmail.com" />
<meta name="publisher-url" content="https://eugene-serb.github.io/" />
<meta name="keywords" content="Eugene Serb, eugene-serb, eugene_serb, eugene.serb, Evgeniy Serb, Евгений Серб, Novorossiysk, Новороссийск, contacts, CV, resume, portfolio, repositories, services, coder, developer, software engineer, development, frontend, backend, fullstack, Gamepad, Vibration, Vibration Master, Massager, Vibrator" />
<meta name="description" content="This is a simple vibration master." />
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Eugene Serb Vibration Master" />
<meta property="og:site_name" content="Eugene Serb Website" />
<meta property="og:description" content="This is a simple vibration master." />
<meta property="og:url" content="https://eugene-serb.github.io/" />
<meta property="og:image" content="https://eugene-serb.github.io/img/og.png" />
<meta property="vk:image" content="https://eugene-serb.github.io/img/og.png" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:creator" content="@eugene_serb" />
<meta name="twitter:title" content="Eugene Serb Vibration Master" />
<meta name="twitter:description" content="This is a simple vibration master." />
<meta name="twitter:image" content="https://eugene-serb.github.io/img/og.png" />
<meta name="google-site-verification" content="qLQbgnmQEfvprDF8WR6oL_b_Qt0R9kKcIEOfHqWlFm8" />
<meta name="yandex-verification" content="e6e0bff7caaa7ecd" />
<meta name="msvalidate.01" content="6E1771734F083E5366205F06314C3577" />
<meta name='wmail-verification' content='46d069b79f9c774ce0bbf55f46aef201' />
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-4NB4LGNNLB"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-4NB4LGNNLB');
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<!-- Yandex.Metrika counter -->
<script>
(function (m, e, t, r, i, k, a) {
m[i] = m[i] || function () { (m[i].a = m[i].a || []).push(arguments) };
m[i].l = 1 * new Date(); k = e.createElement(t), a = e.getElementsByTagName(t)[0], k.async = 1, k.src = r, a.parentNode.insertBefore(k, a)
})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(79722217, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
</script>
<!-- /Yandex.Metrika counter -->
</head>
<body>
<header class="header">
<div class="header-wrapper container">
<div class="logo-wrapper">
<span class="logo-wrapper__logo">Eugene Serb.</span>
</div>
<nav class="menu-wrapper">
<ul class="navigation">
<li class="navigation__item">
<a href="https://eugene-serb.github.io/" target="_self" class="navigation__link">Home</a>
</li>
<li class="navigation__item">
<a href="https://eugene-serb.github.io/#skills" target="_self" class="navigation__link">Skills</a>
</li>
<li class="navigation__item">
<a href="https://eugene-serb.github.io/services.html" target="_self" class="navigation__link">Services</a>
</li>
<li class="navigation__item">
<a href="https://eugene-serb.github.io/projects.html" target="_self" class="navigation__link">Projects</a>
</li>
</ul>
</nav>
</div>
</header>
<main class="page container">
<h1 class="visually-hidden">Eugene Serb Vibration Master</h1>
<section class="post">
<h2 class="post__header">Vibration Master</h2>
<div class="vibration-master">
<div class="content-item content_bordered message-box">
<span id="message"></span>
</div>
<div id="device-box" class="content-item content_bordered device-box">
<h3>Device List</h3>
<div id="device-list" class="device-list"></div>
</div>
<section class="content-item content_bordered controls-banner">
<h3>Disclaimer</h3>
<div class="controls-banner__list">
<span>
The author of the program code and the publisher is not responsible for the possible consequences of using the program. You use the program at your own risk. Please consult your doctor before using the program.
</span>
</div>
</section>
</div>
</section>
</main>
<footer class="footer">
<div class="footer-wrapper container">
<div class="annotation">
<span class="annotation__text">© 2021 2022 Eugene Serb. Content licensed under </span><a href="https://eugene-serb.github.io/vibration-master/LICENSE.md" target="_blank">GNU General Public License v3.0</a><br>
<span class="annotation__text">This site is open source. </span><a href="https://github.com/eugene-serb/vibration-master/" target="_blank">Improve this page.</a>
</div>
</div>
</footer>
<script src="js/vibration-master.js"></script>
<script src="https://eugene-serb.github.io/js/scripts.js"></script>
<noscript><div><img src="https://mc.yandex.ru/watch/79722217" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
</body>
</html>

392
js/vibration-master.js Normal file
View File

@ -0,0 +1,392 @@
/* -------------- */
/* GAMEPAD MASTER */
/* -------------- */
'use strict';
const __PATTERNS = [
/* Constant, 0s, 1s */
{
name: 'Constant Weak',
type: 'Simple',
pattern: [
{
startDelay: 0,
duration: 1000,
weakMagnitude: 1.0,
strongMagnitude: 0.0,
},
],
},
{
name: 'Constant Strong',
type: 'Simple',
pattern: [
{
startDelay: 0,
duration: 1000,
weakMagnitude: 0.0,
strongMagnitude: 1.0,
},
],
},
{
name: 'Constant Max',
type: 'Simple',
pattern: [
{
startDelay: 0,
duration: 1000,
weakMagnitude: 1.0,
strongMagnitude: 1.0,
},
],
},
/* Dotted, 0.1s, 0.1s */
{
name: 'Dotted Weak',
type: 'Simple',
pattern: [
{
startDelay: 100,
duration: 100,
weakMagnitude: 1.0,
strongMagnitude: 0.0,
},
],
},
{
name: 'Dotted Strong',
type: 'Simple',
pattern: [
{
startDelay: 100,
duration: 100,
weakMagnitude: 0.0,
strongMagnitude: 1.0,
},
],
},
{
name: 'Dotted Max',
type: 'Simple',
pattern: [
{
startDelay: 0,
duration: 100,
weakMagnitude: 1.0,
strongMagnitude: 1.0,
},
],
},
/* Short Dashed, 0.1s, 0.25s */
{
name: 'Short Dashed Weak',
type: 'Simple',
pattern: [
{
startDelay: 100,
duration: 250,
weakMagnitude: 1.0,
strongMagnitude: 0.0,
},
],
},
{
name: 'Short Dashed Strong',
type: 'Simple',
pattern: [
{
startDelay: 100,
duration: 250,
weakMagnitude: 0.0,
strongMagnitude: 1.0,
},
],
},
{
name: 'Short Dashed Max',
type: 'Simple',
pattern: [
{
startDelay: 0,
duration: 250,
weakMagnitude: 1.0,
strongMagnitude: 1.0,
},
],
},
/* Long Dashed, 0.1s, 0.5s */
{
name: 'Long Dashed Weak',
type: 'Simple',
pattern: [
{
startDelay: 100,
duration: 500,
weakMagnitude: 1.0,
strongMagnitude: 0.0,
},
],
},
{
name: 'Long Dashed Strong',
type: 'Simple',
pattern: [
{
startDelay: 100,
duration: 500,
weakMagnitude: 0.0,
strongMagnitude: 1.0,
},
],
},
{
name: 'Long Dashed Max',
type: 'Simple',
pattern: [
{
startDelay: 0,
duration: 500,
weakMagnitude: 1.0,
strongMagnitude: 1.0,
},
],
},
];
class Gamepad {
constructor(gamepad, $container, library) {
this.unit = gamepad;
this.$container = $container;
this.library = library;
this.init();
};
init = () => {
this.id = Date.now();
this.isSelected = false;
this.isVibrating = false;
this.isLocked = false;
this.cooldown = 0;
this.index = 0;
this.pattern = [
{
startDelay: 0,
duration: 1000,
weakMagnitude: 1.0,
strongMagnitude: 1.0,
}
];
this.generateBox();
};
delete = () => {
this.$list_item.parentNode.removeChild(this.$list_item);
};
generateBox = () => {
const $list_item = document.createElement('div');
const $info_box = document.createElement('div');
/*const $button = document.createElement('button');*/
$list_item.classList.add('list-item');
$info_box.classList.add('list-item__info');
/*$button.innerText = 'Select';*/
/*$button.addEventListener('click', () => {
this.isSelected = !this.isSelected;
});*/
$list_item.appendChild($info_box);
/*$list_item.appendChild($button);*/
this.$container.appendChild($list_item);
this.$list_item = $list_item;
this.$info_box = $info_box;
this.draw();
};
draw = () => {
this.$info_box.innerHTML = `
<h3>#${this.unit.index + 1}. ${this.unit.id}</h3>
<span>Vibration Actuator: ${this.unit.vibrationActuator ? 'Available' : 'missing'}</span>
<div>
<span>Status: </span>
<span>${this.isVibrating ? 'Vibrating' : 'Idle'}</span>
<span>A / B</span>
<span>Key locked: </span>
<span>${this.isLocked ? 'Yes' : 'No'}</span>
<span>X + Y</span>
<span>Mode: </span>
<span>${this.index + 1}. ${this.library[this.index].name}</span>
<span>LB / RB</span>
</div>`;
if (this.isSelected === true) {
this.$list_item.classList.add('list-item_selected');
} else {
this.$list_item.classList.remove('list-item_selected');
};
};
update = () => {
let gamepads = navigator.getGamepads();
this.unit = gamepads[this.unit.index];
};
reset = () => {
this.isVibrating = false;
this.unit.vibrationActuator.reset();
};
vibrate = async () => {
this.isVibrating = true;
this.pattern = this.library[this.index].pattern;
while (this.isVibrating) {
for (let i = 0; i < this.pattern.length; i++) {
if (this.isVibrating) {
this.unit.vibrationActuator.playEffect('dual-rumble', this.pattern[i]);
await this.sleep(this.pattern[i].startDelay + this.pattern[i].duration + 100);
} else {
return;
};
};
};
};
sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
};
previous = () => {
if (Date.now() >= this.cooldown) {
if (this.index === 0) {
this.index = this.library.length - 1;
} else {
this.index--;
};
this.pattern = this.library[this.index].pattern;
this.cooldown = Date.now() + 500;
};
};
next = () => {
if (Date.now() >= this.cooldown) {
if (this.index === this.library.length - 1) {
this.index = 0;
} else {
this.index++;
};
this.pattern = this.library[this.index].pattern;
this.cooldown = Date.now() + 500;
};
};
lock = () => {
if (Date.now() >= this.cooldown) {
this.isLocked = !this.isLocked;
this.cooldown = Date.now() + 500;
};
};
};
class VibrationMaster {
constructor() {
this.init();
};
init = () => {
this.#DOMs();
if (!this.checkGamepadSupport()) {
console.log(`This browser does not support of gamepads.`);
this.$MESSAGE.innerText = `This browser does not support of gamepads.`;
return;
} else {
console.log(`Press any gamepad's button or connect new gamepad.`);
this.$MESSAGE.innerText = `Press any gamepad's button or connect new gamepad.`;
};
this.gamepads = [];
this.#eventListeners();
this.interval = setInterval(this.eventLoop, 1);
};
eventLoop = () => {
this.update();
this.draw();
this.eventHandler();
};
update = () => {
if (this.gamepads.length > 0) {
this.$MESSAGE_BOX.classList.add('hidden');
this.$DEVICE_BOX.classList.remove('hidden');
this.gamepads.forEach(gamepad => {
gamepad.update();
});
} else {
this.$MESSAGE_BOX.classList.remove('hidden');
this.$DEVICE_BOX.classList.add('hidden');
};
};
draw = () => {
if (this.gamepads.length > 0) {
this.gamepads.forEach(gamepad => {
gamepad.draw();
});
};
};
eventHandler = () => {
if (this.gamepads.length > 0) {
this.gamepads.forEach(gamepad => {
if (gamepad.unit.vibrationActuator) {
if (gamepad.unit.buttons[2].pressed === true &&
gamepad.unit.buttons[3].pressed === true) {
gamepad.lock();
};
if (gamepad.isLocked === false) {
if (gamepad.unit.buttons[0].pressed === true) {
if (gamepad.isVibrating === false) {
gamepad.vibrate();
};
};
if (gamepad.unit.buttons[1].pressed === true) {
gamepad.reset();
};
if (gamepad.unit.buttons[4].pressed === true) {
gamepad.previous();
};
if (gamepad.unit.buttons[5].pressed === true) {
gamepad.next();
};
};
};
});
};
};
checkGamepadSupport = () => {
return 'getGamepads' in window.navigator;
};
#DOMs = () => {
this.$MESSAGE = document.querySelector('#message');
this.$MESSAGE_BOX = document.querySelector('.message-box');
this.$DEVICE_LIST = document.querySelector('#device-list');
this.$DEVICE_BOX = document.querySelector('#device-box');
};
#eventListeners = () => {
window.addEventListener('gamepadconnected', (event) => {
this.gamepads.push(new Gamepad(event.gamepad, this.$DEVICE_LIST, __PATTERNS));
});
window.addEventListener('gamepaddisconnected', (event) => {
this.gamepads.forEach((gamepad, index) => {
if (gamepad.unit.id === event.gamepad.id) {
this.gamepads[index].delete();
this.gamepads.splice(index, 1);
};
});
});
};
};
/* -------------- */
/* INITIALIZATION */
/* -------------- */
const VIBRATION_MASTER = new VibrationMaster();