how to add tooltip to autodesk forge sprite viewable? - javascript

i am trying to add tooltip and data over hover of a sprite inside using the data visualization api. I have achieved adding the sprite viewable, but it is not mentioned as to how we add a tooltip on hover. Docs just mention there is a DATAVID_OBJECT_HOVERING event that is triggered to the viewer object, and we can add a callback on it. Also the code is not clear. Attaching hyperion demo's code below which only adds a callback to the onhover event on sprite and no mention of tooltip and data passed to it. Where in the code that is present as in this website i am seeing the tooltip with some data. this is the link https://hyperion.autodesk.io/ and code is this https://github.dev/Autodesk-Forge/forge-dataviz-iot-reference-app
async function onModelLoaded(viewer) {
const dataVizExt = viewer.getExtension("Autodesk.DataVisualization");
const DATAVIZEXTN = Autodesk.DataVisualization.Core;
var styleMap = {};
// Create model-to-style map from style definitions.
Object.entries(SensorStyleDefinitions).forEach(([type, styleDef]) => {
styleMap[type] = new DATAVIZEXTN.ViewableStyle(
DATAVIZEXTN.ViewableType.SPRITE,
new THREE.Color(styleDef.color),
styleDef.url
);
});
const viewableData = new DATAVIZEXTN.ViewableData();
viewableData.spriteSize = 16;
let startId = 1;
devices.forEach((device) => {
let style = styleMap[device.type] || styleMap["default"];
const viewable = new DATAVIZEXTN.SpriteViewable(device.position, style, startId);
viewableData.addViewable(viewable);
startId++;
});
await viewableData.finish();
dataVizExt.addViewables(viewableData);
/**
* Called when a user clicks on a Sprite Viewable
* #param {Event} event
*/
function onItemClick(/* event */) {}
/**
* Called when a user hovers over a Sprite Viewable
* #param {Event} event
*/
function onItemHovering(event) {
console.log("Show tooltip here", event.dbId);
}
const DataVizCore = Autodesk.DataVisualization.Core;
viewer.addEventListener(DataVizCore.MOUSE_CLICK, onItemClick);
viewer.addEventListener(DataVizCore.MOUSE_HOVERING, onItemHovering);
}

May check out my BIM360SensorTooltip at forge-bim360-assets.viewer/BIM360IotConnectedExtension.js#L300. The core concept is like the following
Create your custom tooltip by JavaScript DOM API.
To add a tooltip for a sprite, just add some codes in your onItemHovering event to create/show your custom tooltip.
Here is an example of my BIM360SensorTooltip (removed BIM360 prefix):
class SensorTooltip extends THREE.EventDispatcher {
constructor(parent) {
super();
this.parent = parent;
this.init();
}
get viewer() {
return this.parent.viewer;
}
get dataVizTool() {
return this.parent.dataVizTool;
}
init() {
const container = document.createElement('div');
container.classList.add('bim360-sensor-tooltip');
this.container = container;
const bodyContainer = document.createElement('div');
bodyContainer.classList.add('bim360-sensor-tooltip-body');
container.appendChild(bodyContainer);
this.bodyContainer = bodyContainer;
bodyContainer.innerHTML = 'No Data';
this.viewer.container.appendChild(container);
}
setVisible(visible) {
if (visible) {
this.bodyContainer.classList.add('visible');
} else {
this.bodyContainer.classList.remove('visible');
}
}
setPosition(point) {
const contentRect = this.bodyContainer.getBoundingClientRect();
const offsetX = contentRect.width / 2;
const spriteSize = this.dataVizTool.viewableData.spriteSize;
const offsetY = contentRect.height + 0.7 * spriteSize / this.parent.viewer.getWindow().devicePixelRatio;
const pos = new THREE.Vector3(
point.x - offsetX,
point.y - offsetY,
0
);
this.container.style.transform = `translate3d(${pos.x}px, ${pos.y}px, ${pos.z}px)`;
}
setPositionByWordPoint(point) {
this.setPosition(this.viewer.worldToClient(point));
}
async show(sensor) {
if (!sensor) return;
this.bodyContainer.innerHTML = '';
const nameSpan = document.createElement('span');
nameSpan.classList.add('bim360-sensor-tooltip-name');
this.bodyContainer.appendChild(nameSpan);
const assetInfo = this.parent.dataProvider.assetInfoCache[sensor.externalId];
let nameString = 'Unknown asset';
if (assetInfo) {
nameString = `Asset [${assetInfo.assetId}]`;
}
nameSpan.innerHTML = `${nameString} ${sensor.name}`;
const valueSpan = document.createElement('span');
valueSpan.classList.add('bim360-sensor-tooltip-value');
this.bodyContainer.appendChild(valueSpan);
let cachedData = this.parent.dataHelper.getDataFromCache(sensor.id, sensor.name);
if (cachedData) {
let value = Utility.getClosestValue(cachedData, Utility.getTimeInEpochSeconds(this.parent.currentTime));
let valueString = `${value.toFixed(2)}`;
if (sensor.dataUnit)
valueString += ` ${sensor.dataUnit}`;
valueSpan.innerHTML = valueString;
}
this.setVisible(true);
this.setPosition(this.viewer.worldToClient(sensor.position));
}
hide() {
this.bodyContainer.innerHTML = 'No Data';
this.setVisible(false);
}
}
const tooltip = new SensorTooltip(yourExtesionLoadedTheDataVisulationExtesion);
const onSensorHovered => (event) {
if (event.hovering && dbId2DeviceIdMap) {
const deviceId = dbId2DeviceIdMap[event.dbId];
const { sensors } = dataProvider;
if (!sensors || sensors.length <= 0) return;
const sensor = sensors.find(s => s.externalId == deviceId);
if (!sensor) return;
tooltip.show(sensor);
} else {
tooltip.hide();
}
};
viewer.addEventListener(
Autodesk.DataVisualization.Core.MOUSE_HOVERING,
onSensorHovered
);
and its CSS style see https://github.com/yiskang/forge-bim360-assets.viewer/blob/bim360-iot-connected/bim360assets/wwwroot/css/main.css#L182
/** DataViz Sensor Tooltip **/
/*https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_tooltip*/
.bim360-sensor-tooltip {
visibility: hidden;
position: absolute;
display: inline-block;
border-bottom: 1px dotted black;
top: 0;
left: 0;
width: 170px;
}
.bim360-sensor-tooltip .bim360-sensor-tooltip-body {
width: 170px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
left: 50%;
margin-left: -85px;
opacity: 0;
transition: opacity 0.3s;
visibility: hidden;
width: 170px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
opacity: 0;
transition: opacity 0.3s;
font-size: 12px;
}
.bim360-sensor-tooltip .bim360-sensor-tooltip-body::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #555 transparent transparent transparent;
}
.bim360-sensor-tooltip .bim360-sensor-tooltip-body.visible {
visibility: visible;
opacity: 1;
}
.bim360-sensor-tooltip-name {
display: flex;
justify-content: center;
font-weight: bold;
font-size: 12px;
padding-top: 1px;
padding-bottom: 3px;
}
.bim360-sensor-tooltip-value {
font-size: 13px;
display: flex;
justify-content: center;
}

Related

How to add timer inside the progress bar

I want to add a timer which decrease Automatically (like 10 seconds, 9 seconds... till 0 seconds) but the progress bar will increase. And I am new to javascript, and the below code also copied from another site , so please help me in adding timer inside the progress bar
Till now I did this code
I want to make like this
Demo
<div class="progress"></div>
<style>
.progress-bar {
height: 20px;
background: #1da1f2;
box-shadow: 2px 14px 15px -7px rgba(30, 166, 250, 0.36);
border-radius: 50px;
transition: all 0.5s;
}
.progress {
width: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: start;
background: #e6e9ff;
border-radius: 20px;
box-shadow: 0px 10px 50px #abb7e9;
}
</style>
<script>
/*
* (class)Progress<nowValue, minValue, maxValue>
*/
//helper function-> return <DOMelement>
function elt(type, prop, ...childrens) {
let elem = document.createElement(type);
if (prop) Object.assign(elem, prop);
for (let child of childrens) {
if (typeof child == "string") elem.appendChild(document.createTextNode(child));
else elem.appendChild(elem);
}
return elem;
}
//Progress class
class Progress {
constructor(now, min, max, options) {
this.dom = elt("div", {
className: "progress-bar"
});
this.min = min;
this.max = max;
this.intervalCode = 0;
this.now = now;
this.syncState();
if(options.parent){
document.querySelector(options.parent).appendChild(this.dom);
}
else document.body.appendChild(this.dom)
}
syncState() {
this.dom.style.width = this.now + "%";
}
startTo(step, time) {
if (this.intervalCode !== 0) return;
this.intervalCode = setInterval(() => {
console.log("sss")
if (this.now + step > this.max) {
this.now = this.max;
this.syncState();
clearInterval(this.interval);
this.intervalCode = 0;
return;
}
this.now += step;
this.syncState()
}, time)
}
end() {
this.now = this.max;
clearInterval(this.intervalCode);
this.intervalCode = 0;
this.syncState();
}
}
let pb = new Progress(15, 0, 100, {parent : ".progress"});
//arg1 -> step length
//arg2 -> time(ms)
pb.startTo(5, 500);
//end to progress after 5s
setTimeout( () => {
pb.end()
}, 10000)
</script>
I think the core problem is that the code you copied is overly complicated especially for beginners. What I would recommend is to start from what you know and build up.
Here is the functionality you want written using only core principles of JavaScript and CSS.
let initialTime = 10; //All time in seconds
let timeLeft = initialTime;
let interval;
let progressBarTextElement = document.getElementById('progress-bar-text');
let progressBarElement = document.getElementById('progress-bar');
function render() {
let progressPercentage = (1 - (timeLeft / initialTime) ) * 100;
progressBarElement.style.width = progressPercentage + '%';
progressBarTextElement.innerHTML = timeLeft + 's';
}
function tick() {
timeLeft = timeLeft - 1;
if(timeLeft <= 0) {
clearInterval(interval); //Stops interval
}
render(); //Updates html
}
function startProgressBar() {
interval = setInterval(tick, 1000); //Will call tick every second
render();
}
startProgressBar();
html {font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;}
.progress-bar-continer {
height: 80px;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
background-color: #406086;
}
.progress-bar {
background-color: #1b3e80;
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 0%;
transition: width 1s; /* Makes progressBar smooth */
transition-timing-function: linear; /* Try to remove this line for more tick tock feel */
}
.progress-bar-text {
color: white;
font-size: 24px;
font-weight: 700;
position: relative;
z-index: 1;
}
<div class="progress-bar-continer">
<div class="progress-bar" id="progress-bar"></div>
<div class="progress-bar-text" id="progress-bar-text"></div>
<div>
Try to understand the code best you can and you will have no problems adding any features you want on top of it. All the fancy stuff will come later with experience.

Iterate HTML classes to create class objects with javascript

class CVP {
constructor(obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
// player
this.player = document.querySelector(P);
// video el
this.video = document.querySelector(V);
// player controller
this.playerController = document.querySelector(PC);
// play controlers
this.bigPlayBtn = document.querySelector(BPB);
this.miniPlayBtn = document.querySelector(MPB);
// volume controllers
this.volumeRangeController = document.querySelector(VRC);
this.volumeBtnController = document.querySelector(VBC);
// fullscreen
this.fullscreen = document.querySelector(FS);
// progress bar
this.progress = document.querySelector(PROG);
this.progressCurr = document.querySelector(PROGC);
// time displayers
this.timeWhole = document.querySelector(TW);
this.timeCurr = document.querySelector(TC);
// bool vars
this.isMousedown = false;
this.isFullScreen = false;
// timer vars
this.timer = '';
this.timerTime = 1000;
}
setIsMouseDown = () => {
this.isMousedown = !this.isMousedown;
};
bigPlayBtnHandler = () => {
this.bigPlayBtn.classList.toggle('HideBigPlayBtn');
};
showHideControlls = () => {
this.player.classList.toggle('paused');
};
static changeIcon(target, remove, add) {
target.classList.remove(remove);
target.classList.add(add);
}
changePlayIcon() {
console.log(this.video.src);
const target = this.miniPlayBtn;
let icon = this.video.paused ? 'play' : 'pause';
if (icon === 'pause') {
CVP.changeIcon(target, 'play', 'pause');
} else {
CVP.changeIcon(target, 'pause', 'play');
}
}
changeVolumeIcon(volElIconID) { // not in use
const target = document.querySelector(volElIconID);
if (this.video.muted) {
CVP.changeIcon(target, 'max', 'mute');
} else {
CVP.changeIcon(target, 'mute', 'max');
}
}
handleVideoTime(time) {
// minutes and seconds So Far
let hours;
let min = Math.floor(time / 60);
let sec = Math.floor(time % 60);
let output = '';
sec = (sec < 10) ? sec = `0${sec}` : sec;
min = (min < 10) ? min = `0${min}` : min;
output = `${min}:${sec}`;
return output;
}
rangeVolumeController() {
cvp.video[this.name] = this.value;
if (cvp.volumeRangeController.value == 0) {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
} else {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
}
}
btnVolumeController() {
if (cvp.video.muted) {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
cvp.volumeRangeController.value = 1;
cvp.video.volume = 1;
} else {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
cvp.volumeRangeController.value = 0;
}
}
hideWhenFullScreen() {
if (this.isFullScreen) {
this.playerController.hidden = false; // or this.playerController.style.bottom = '0px';
document.body.style.cursor = 'auto';
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.playerController.hidden = true; // or this.playerController.style.bottom = '-50px';
document.body.style.cursor = 'none';
}, this.timerTime);
} else {
this.playerController.hidden = false; // or this.playerController.style.bottom = '0px';
document.body.style.cursor = 'auto';
}
}
toggleFullScreen() {
if (this.isFullScreen) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else {
console.error('Unable to find a fullscreen exit method.');
}
} else {
if (this.player.requestFullscreen) {
this.player.requestFullscreen();
} // standard
else if (this.player.webkitRequestFullscreen) {
this.player.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (this.player.mozRequestFullScreen) {
this.player.mozRequestFullScreen();
} else if (this.player.msRequestFullscreen) {
this.player.msRequestFullscreen();
} else {
console.error('Unable to find a fullscreen request method');
}
}
}
toggleFullScreenHelper() {
this.player.classList.toggle('isFullScreen');
this.isFullScreen = !this.isFullScreen;
}
togglePlay() {
const action = this.video.paused ? 'play' : 'pause';
this.video[action]();
this.playerController.style.bottom = '0px';
this.changePlayIcon();
this.showHideControlls();
}
showVideoTime() {
const {
currentTime,
duration
} = cvp.video;
const pct = (currentTime / duration) * 100;
cvp.progressCurr.style.width = pct + '%';
cvp.timeCurr.innerText = cvp.handleVideoTime(currentTime.toFixed(0));
cvp.timeWhole.innerText = cvp.handleVideoTime(duration);
}
scrub(e) {
console.log(e);
console.log('e', e, 'this', this);
let seconds = (e.offsetX / this.progress.offsetWidth) * this.video.duration;
this.video.currentTime = seconds;
}
loadVideo() {
// il metodo è da applciare assieme a un loader e ad altre gestioni degli eventi video
// esempi di loader: https://www.w3schools.com/howto/howto_css_loader.asp
// metodi video: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadeddata_event
// per i tooltips: https://www.w3schools.com/howto/howto_css_tooltip.asp
console.log(this.video.src);
}
init() {
// Hook up the Event Listeners
// toggle Play
this.video.addEventListener('click', this.togglePlay.bind(this));
this.miniPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.bigPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.video.addEventListener('loadeddata', this.loadVideo.bind(this));
// bigPlayBtn show/hide handler
this.video.addEventListener('click', this.bigPlayBtnHandler);
this.bigPlayBtn.addEventListener('click', this.bigPlayBtnHandler);
this.miniPlayBtn.addEventListener('click', this.bigPlayBtnHandler);
// time update
this.video.addEventListener('timeupdate', this.showVideoTime);
// progress bar events
this.progress.addEventListener('click', this.scrub.bind(this));
this.progress.addEventListener('mousemove', (e) => this.isMousedown && this.scrub(e));
this.progress.addEventListener('mousedown', this.setIsMouseDown);
this.progress.addEventListener('mouseup', this.setIsMouseDown);
// fullscreen
this.fullscreen.addEventListener('click', this.toggleFullScreen.bind(this));
// volume controllers
this.volumeBtnController.addEventListener('click', this.btnVolumeController);
this.volumeRangeController.addEventListener('change', this.rangeVolumeController);
this.volumeRangeController.addEventListener('mousemove', this.rangeVolumeController);
// cross-browser fullscreen hanlder
const browsersFullScreen = [
'fullscreenchange',
'mozfullscreenchange',
'webkitfullscreenchange',
'msfullscreenchange',
];
browsersFullScreen.forEach(browser => document.addEventListener(browser, this.toggleFullScreenHelper.bind(this)));
// player show/hide controlls on mousemove event
this.player.addEventListener('mousemove', this.hideWhenFullScreen.bind(this));
}
}
const initializeCVP = {
P: '.CVP',
V: '.video',
PC: '.playerController',
BPB: '.bigPlayBtn',
MPB: '.miniPlayBtn',
VRC: '.volumeRC',
VBC: '.volumeBC',
FS: '.fullscreen',
PROG: '.progress',
PROGC: '.progressCurr',
TW: '.timeWhole',
TC: '.timeCurr',
};
let cvp = new CVP(initializeCVP);
cvp.init();
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
height: 100vh;
background-image: linear-gradient(to top, #dad4ec 0%, #dad4ec 1%, #f3e7e9 100%);
/* background-image: linear-gradient(-20deg, #f794a4 0%, #fdd6bd 100%); */
}
[hidden] {
display: none !important;
}
.player {
display: flex;
justify-content: center;
border-radius: 10px;
width: 700px;
max-height: 100%;
position: relative;
font-size: 16px;
overflow: hidden;
text-align: center;
background: #000;
color: #fff;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.4), 0 10px 20px -3px rgba(0, 0, 0, 0.2);
margin-bottom: 5vh;
}
/* This css is only applied when fullscreen is active. */
.player.isFullScreen {
max-width: none;
max-height: none;
width: 100%;
height: 100%;
background: #000;
}
.player.isFullScreen video {
width: 100%;
height: auto;
}
.video {
max-width: 100%;
max-height: 100%;
-o-object-fit: fill;
object-fit: fill;
/* These are the actual dimensions of the video. Avoid the awkward element resize after the video loads. */
/* width: 640px;
height: 358px; */
}
.playerController {
display: flex;
align-items: center;
justify-content: space-evenly;
width: 98%;
max-width: 1000px;
/* limiting in fullscreen mode */
position: absolute;
background: rgba(51, 51, 51, 0.8);
color: #fff;
border-radius: 10px;
margin-bottom: 1%;
z-index: 2147483649;
transform: translateY(150px);
transition: .2s;
bottom: -50px;
/* 0 to show in preview */
}
.playerController * {
color: rgba(255, 255, 255, 0.9);
}
.playerController button {
background: none;
border: 0;
outline: 0;
text-align: center;
cursor: pointer;
}
.icon {
font-size: 16px;
font-family: 'Font Awesome 5 Free';
font-weight: 900;
}
.player:hover .playerController,
.player.paused .playerController {
transform: translateY(0);
}
.player__btn {
max-width: 50px;
}
.play::before {
content: '\f04b';
}
.pause::before {
content: '\f04c';
}
.progress {
/* position: relative; */
width: 50%;
cursor: pointer;
user-select: none;
height: 10px;
/* or 15px */
background: rgba(160, 154, 154, 0.8);
overflow: hidden;
border-radius: 50px;
}
.progressCurr {
height: 100%;
width: 0;
background: rgba(255, 255, 255, 0.9);
/* flex: 0;
flex-basis: 0%; */
/* old styles yellow progressCurr bg flex-basic insted of width*/
}
.fullscreen::before {
font-family: 'FontAwesome';
content: '\f065';
font-size: 16px
}
.player.isFullScreen .playerController .fullscreen::before {
content: '\f066';
}
.timeCurr,
.timeWhole {
font-size: 14px;
user-select: none;
}
.volumeContainer {
display: flex;
}
.volumeBC {
cursor: pointer;
padding-right: 5px;
}
.volumeRC {
width: 10px;
height: 30px;
}
.max::before {
content: '\f028'
}
.mute::before {
content: '\f6a9'
}
.bigPlayBtn {
font-size: 80px;
position: absolute;
top: 50%;
left: 50%;
cursor: pointer;
box-sizing: border-box;
transform: translate(-50%, -50%);
transition: .5s;
}
.bigPlayBtn::before {
content: '\f04b';
}
.HideBigPlayBtn {
cursor: default;
opacity: 0;
transform: translate(-50%, -50%) scale(1.2);
}
<div class='player paused CVP'>
<video class='video' src='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' width='700' height='350' poster='https://i.ytimg.com/vi/A1sVB8XBNDo/maxresdefault.jpg'>
</video>
<div class='bigPlayBtn icon'></div>
<div class='playerController'>
<button class='player__btn miniPlayBtn icon' title='Play/Pause'></button>
<div class='timeCurr'>00:00</div>
<div class='progress'>
<span id='buffered'>
<div class='progressCurr'></div>
</span>
</div>
<div class='timeWhole'>--:--</div>
<div class='volumeContainer'>
<button class='player__btn max volumeBC icon'></button>
<input type='range' name='volume' class='volume volumeRC' min='0' max='1' step='0.01' value='1' title='Volume' />
</div>
<button class='player__btn fullscreen icon' title='Full Screen'></button>
</div>
</div>
<div class='player paused CVP'>
<video class='video' src='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4' width='700' height='350' poster='https://i.ytimg.com/vi/A1sVB8XBNDo/maxresdefault.jpg'>
</video>
<div class='bigPlayBtn icon'></div>
<div class='playerController'>
<button class='player__btn miniPlayBtn icon' title='Play/Pause'></button>
<div class='timeCurr'>00:00</div>
<div class='progress'>
<span id='buffered'>
<div class='progressCurr'></div>
</span>
</div>
<div class='timeWhole'>--:--</div>
<div class='volumeContainer'>
<button class='player__btn max volumeBC icon'></button>
<input type='range' name='volume' class='volume volumeRC' min='0' max='1' step='0.01' value='1' title='Volume' />
</div>
<button class='player__btn fullscreen icon' title='Full Screen'></button>
</div>
</div>
Sorry if I'm disturbing you for something that will probably appear you very simple, but I can't find a way to solve the problem on my own.
And, maybe, the title is not appropriated...
Well, I have a class with a constructor that works with HTML tag VIDEO, named CVP. It creates methods to act on video controls. The initial method consist in hooking the event listeners to HTML controls'classes.
Anyway, with a single VIDEO tag in the HTML code, it works correctly, but I don't know how to iterate the class on any VIDEO when there are many of them. This is an examples of the VIDEO tag:
<div class='player paused CVP'>
<video class='video' src="../../video/pietro.mp4">
</video>
etc...
This is the beginnig of the JS CLASS:
class CVP {
constructor (obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
this.player = document.querySelector(P);
this.video = document.querySelector(V);
etc..
The class ends this way:
(many other methods...)
init () {
// Hook up the Event Listeners
// toggle Play
this.video.addEventListener('click', this.togglePlay.bind(this));
this.miniPlayBtn.addEventListener('click', this.togglePlay.bind(this));
this.bigPlayBtn.addEventListener('click', this.togglePlay.bind(this));
etc...
}
}
Then, there's a inizialization's variable:
const initializeCVP = {
P: '.CVP',
V: '.video',
PC: '.playerController',
etc...
};
To add a single VIDEO tag:
const cvp = new CVP(initializeCVP);
cvp.init();
In this case it works perfectly. But, How can I assign the JS class to many VIDEO tags?
I tried this way, but it doesn't work:
window.onload = function () {
const CVPs = document.querySelectorAll('.CVP');
CVPs.forEach((cvp) => {
cvp = new CVP(initializeCVP);
cvp.init();
});
};
Is it clear? I hope so. I'm quite a beginner, so plase, don't complain...
Change your constructor so it takes elements rather than selectors. The caller can call document.querySelector() or document.querySelectorAll() when creating the initialization object.
class CVP {
constructor (obj) {
const { P, V, PC, BPB, MPB, VRC, VBC, FS, PROG, PROGC, TW, TC } = obj;
this.player = P;
this.video = V;
etc..
}
}
window.onload = function() {
const players = document.querySelectorAll('.player');
players.forEach(player => {
const initializeCVP = {
P: player,
V: player.querySelector('.video'),
PC: player.querySelector('.playerController'),
BPB: player.querySelector('.bigPlayBtn'),
MPB: player.querySelector('.miniPlayBtn'),
VRC: player.querySelector('.volumeRC'),
VBC: player.querySelector('.volumeBC'),
FS: player.querySelector('.fullscreen'),
PROG: player.querySelector('.progress'),
PROGC: player.querySelector('.progressCurr'),
TW: player.querySelector('.timeWhole'),
TC: player.querySelector('.timeCurr'),
};
const cvp = new CVP(initializeCVP);
cvp.init();
});
};
I found a way to fix what #Barman and #RandyCasburn pointed out regarding the single 'cvp' istance connected to class methods.
First, I applied #Barman suggestion, so to iterate all players has in the previous answer.
Then, I put a global array to collect the single 'cvp', with a couple of other new variables:
let cvpItems = [];
let cvpItem;
let currentCVP;
Then, I pushed all player istances in the HTML, applying to anyone an addEventListener. The window.onload code is changed in this way:
window.onload = function () {
const players = document.querySelectorAll('.player');
players.forEach(player => {
const initializeCVP = {
P: player,
V: player.querySelector('.video'),
PC: player.querySelector('.playerController'),
BPB: player.querySelector('.bigPlayBtn'),
MPB: player.querySelector('.miniPlayBtn'),
VRC: player.querySelector('.volumeRC'),
VBC: player.querySelector('.volumeBC'),
FS: player.querySelector('.fullscreen'),
PROG: player.querySelector('.progress'),
PROGC: player.querySelector('.progressCurr'),
TW: player.querySelector('.timeWhole'),
TC: player.querySelector('.timeCurr'),
};
cvpItem = new CVP(initializeCVP);
cvpItems.push(cvpItem);
cvpItem.init();
player.addEventListener('click', currentItem, false);
});
};
With the currentItem function I find the current player iteration using the title attribute in the HTML video tags:
let currentItem = function () {
for (var i = 0; i < cvpItems.length; i++) {
if (cvpItems[i].video.title == this.querySelectorAll('.video')[0].title) {
currentCVP = i;
break;
}
}
};
So, now I can identify the specific player in the JS class. For examples, the previous rangeVolumeController method changes from:
rangeVolumeController () {
cvp.video[this.name] = this.value;
if (cvp.volumeRangeController.value == 0) {
cvp.video.muted = true;
CVP.changeIcon(cvp.volumeBtnController, 'max', 'mute');
} else {
cvp.video.muted = false;
CVP.changeIcon(cvp.volumeBtnController, 'mute', 'max');
}
}
to
rangeVolumeController () {
console.log(CVP, this, event.target);
cvpItems[currentCVP].video[this.name] = this.value;
if (cvpItems[currentCVP].volumeRangeController.value == 0) {
cvpItems[currentCVP].video.muted = true;
CVP.changeIcon(cvpItems[currentCVP].volumeBtnController, 'max', 'mute');
} else {
cvpItems[currentCVP].video.muted = false;
CVP.changeIcon(cvpItems[currentCVP].volumeBtnController, 'mute', 'max');
}
}
It works! But, I know, it's not an elegant way for coding. If someone wants to propose a better or more "elegant" solution, I'll be grateful.

In Chrome extension Manifest v3, how do i instruct manifest.json to run my extension only on certain webpages without using activeTab permission?

Whenever i hover over my ext icon, i get the tooltip "Wants access to this site" which is wrong because it should want access only on youtube.com/watch?v=* (and it's another story that Manifest 'match' refuses to accept https://www.youtube.com/watch?v=* as a valid URL)
This is what i'm currently doing:
// manifest.json
{
"name": "YouTube Overlay",
"version": "0.1",
"manifest_version" : 3,
"description": "Lays overlay on YouTube, can be used for guitar chords/lyrics.",
"background" : {
"service_worker" : "bg.js"
},
"action": {},
"permissions": ["activeTab", "scripting"],
"web_accessible_resources": [{
"resources": ["funcsTBInsertedIntoDOM.js"],
"matches": ["https://www.youtube.com/*"]
}]
}
// bg.js
chrome.action.onClicked.addListener(function (tab) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['yt.js'],
});
});
yt.js when executed above, injects a bunch of HTML elems & CSS rules into DOM. It also injects funcsTBInsertedIntoDOM.js (specified in web_accessible_resources in the manifest.json above) into DOM, which contains function definitions for the injected HTML buttons.
So basically whenever the user clicks on my ext icon, bg.js executes, which in turn executes yt.js. When the user clicks while on a YouTube video, it works fine. But otherwise it throws errors in the console naturally. So how do i instruct the manifest to execute bg.js ONLY on YouTube videos? (it shouldn't even run on other YouTube pages, just only when user is on a video page).
Also, i got a rejection notice from Google Web Store for my extension:
Violation(s):
Use of Permissions:
Violation reference ID: Purple Potassium
Violation: Requesting but not using the following permission(s):
activeTab
How to rectify: Remove the above permission(s)
But if i remove activeTab permission, my extension doesn't work at all.
If someone could propose a solution with both of these problems in mind, i'd be very grateful. Thank you for reading.
Adding additional code to help make it easier:
This is yt.ts:
// all global variables are 'var' instead of let or const because the delete command works only on var
var isFullScreen = false;
var extensionActivated = false;
var resCheckerID:number;
var chordsTALeftPaddingNonFS = chordsTALeftPaddingNonFSOnExit;
var chordsTALeftPaddingFS = "0px";
var thisIsThe1stExitAfterFS = true;
var activateExtension = () => {
console.log("YouTube Overlay activated.");
let scriptElemForASBtn = document.createElement("style");
let innardsForASBtn = styleForAsBtn;
scriptElemForASBtn.innerHTML = innardsForASBtn;
document.head.appendChild(scriptElemForASBtn);
const videoElem = document.querySelector("video");
const vidBottomPanel = document.querySelector(".ytp-chrome-bottom");
const progBarPadding = document.querySelector(".ytp-progress-bar-padding");
const getIdealChordsDivStyles = (isFullScreen:boolean) => {
let vidDims = videoElem.getBoundingClientRect();
let progBarPaddingDims = progBarPadding.getBoundingClientRect();
if (isFullScreen){
console.log("fullscreen detected")
thisIsThe1stExitAfterFS = true;
chordsTALeftPaddingNonFS = chordsTA.style.paddingLeft; // saving this for next nonFS
chordsTA.style.paddingLeft = chordsTALeftPaddingFS; // assigning this from prev FS
return `overflow: hidden; position: absolute; z-index: 1111; width: 100vw; height: ${progBarPaddingDims.y - vidDims.y + (progBarPaddingDims.height/2)}px`;
} else {
try {
if(thisIsThe1stExitAfterFS)
chordsTALeftPaddingFS = chordsTA.style.paddingLeft;
chordsTA.style.paddingLeft = chordsTALeftPaddingNonFS;
thisIsThe1stExitAfterFS = false;
} catch {} // saving this for next FS. on first run it obsly won't be able to find chordsTA.
return `overflow: hidden; position: absolute; z-index: 1111; left: ${vidDims.x}px; top: ${vidDims.y}px; width: ${vidDims.width}px; height: ${progBarPaddingDims.y - vidDims.y + (progBarPaddingDims.height/2)}px`;
}
}
// creating the chords div
let chordsDiv = document.createElement('div');
chordsDiv.style.cssText = getIdealChordsDivStyles(isFullScreen);
chordsDiv.setAttribute("id", "chordsDiv");
let htmlInnards = `
<div id="chordsCtrls" onmouseover="unhideChordsCtrls();" onmouseout="hideChordsCtrls();" style="z-index: 1112; height: ${vidBottomPanel.getBoundingClientRect().height}px; position: absolute; display: inline-block;">
<a id="asBtn" onclick="toggleAutoScroll()" class="btn-flip" data-back="Turn on" data-front="Auto-Scroll Off"></a>
<a id="decTxtSize" class="btn noselect" onclick="decTxtSize();">Tᵀ</a>
<a id="incTxtSize" class="btn noselect" onclick="incTxtSize();">ᵀT</a>
<a id="decIndent" class="btn noselect" onclick="decIndent();">¶-</a>
<a id="incIndent" class="btn noselect" onclick="incIndent();">¶+</a>
</div>
<textarea onkeyup="checkTAWidth();" onclick="checkTAWidth();" id="chordsTA" spellcheck="false" style="position:absolute; left:50%; transform: translate(-50%,0); white-space: pre; overflow-wrap: normal; overflow-x: scroll; font-family: Roboto Mono,monospace; background-color: rgba(0, 0, 0, 0.35); color: white; height: 100%; min-width:10vw; font-size: ${window.screen.height*0.026}px;" placeholder="\n\nPaste\nyour\nchords/lyrics\nin\nhere!">
`
chordsDiv.innerHTML = htmlInnards;
document.body.appendChild(chordsDiv);
chordsTA.value = lyricsOnExit; // doing in convoluted way because i cant fig it out :S
if (chordsTA.value === "undefined") chordsTA.value = "";
chordsTA.scrollTop = lyricsLocOnExit;
chordsTA.style.fontSize = lyricsSizeOnExit;
chordsTA.style.paddingLeft = chordsTALeftPaddingNonFS;
console.log("Lyrics reinstated, if any.");
// hiding the scrollbar of chords div & textarea
let styleForScrollbarHiding = `#chordsDiv::-webkit-scrollbar, #chordsTA::-webkit-scrollbar {height: 0; width: 0;}`;
let styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = styleForScrollbarHiding;
document.head.appendChild(styleSheet);
// auto sizing of chords div
function resCheck() {
let vidDims = videoElem.getBoundingClientRect();
let chordsDims = chordsDiv.getBoundingClientRect();
let requisiteHtForChordsDiv = vidDims.height - vidBottomPanel.getBoundingClientRect().height- (progBarPadding.getBoundingClientRect().height/2);
if (((chordsDims.x !== vidDims.x || chordsDims.width !== vidDims.width) && chordsDims.x !== 0) || (chordsDims.x === 0 && chordsDims.x !== vidDims.x)) { // base cond's latter gets True when exiting from FS. Base's former's former checks in non fullScn mode if x or width is wrong.
if (isFullScreen && vidDims.y === 0) return;
console.log("Video dimensions changed detected, redrawing overlay.");
isFullScreen = vidDims.y === 0 ? true : false;
chordsDiv.style.cssText = getIdealChordsDivStyles(isFullScreen);
}
}
resCheckerID = setInterval(resCheck, 2000);
// addding my JS functions to the youtube HTML webpage/DOM
let s = document.createElement('script');
// TODO: add "scriptName.js" to web_accessible_resources in manifest.json
s.src = chrome.runtime.getURL('funcsTBInsertedIntoDOM.js');
(document.head || document.documentElement).appendChild(s);
}
var styleForAsBtn = `
#asBtn {
opacity: 1;
outline: 0;
color: #FFFFFF;
line-height: 40px;
position: relative;
text-align: center;
letter-spacing: 1px;
display: inline-block;
text-decoration: none;
text-transform: uppercase;
font-size: small;
font-weight: bold;
}
#asBtn:hover:after {
opacity: 1;
transform: translateY(0) rotateX(0);
}
#asBtn:hover:before {
opacity: 0;
transform: translateY(50%) rotateX(90deg);
}
#asBtn:after {
top: 0;
left: 0;
opacity: 0;
width: 100%;
color: #000000;
background: #BCBCBC;
display: block;
transition: 0.25s;
position: absolute;
content: attr(data-back);
transform: translateY(-50%) rotateX(90deg);
}
#asBtn:before {
top: 0;
left: 0;
opacity: 1;
color: #FFFFFF;
background: #323237;
display: block;
padding: 0 5px;
line-height: 40px;
transition: 0.25s;
position: relative;
content: attr(data-front);
transform: translateY(0) rotateX(0);
}
/* CSS for other buttons */
.btn{
display: inline-block;
color: #FFFFFF;
width: 40px;
height: 40px;
line-height: 40px;
border-radius: 50%;
text-align: center;
vertical-align: middle;
overflow: hidden;
font-weight: bold;
background-image: -webkit-linear-gradient(#666666 0%, #323237 100%);
background-image: linear-gradient(#666666 0%, #323237 100%);
text-shadow: 1px 1px 1px rgba(255, 255, 255, 0.66);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.28);
font-size: x-large;
cursor: pointer;
}
.btn:active{
color: #000000;
}
.btn:hover {
text-align: center;
opacity: 1;
background-image: -webkit-linear-gradient(#999999 0%, #323237 100%);
background-image: linear-gradient(#999999 0%, #323237 100%);
}
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Edge, Opera and Firefox */
}
#incTxtSize, #decTxtSize{
font-size: large;
}
#chordsCtrls>*{
transition: transform 0.1s linear;
}
textarea::placeholder {
color: white;
opacity: 0.8;
font-size: 4vh;
}
#contentContainer.tp-yt-app-drawer[swipe-open].tp-yt-app-drawer::after{
visibility: hidden;
}
`
// the last css property above is hiding a thin left side vertical area which otherwise causes chordsCtrls not to show up if mouse is on extreme left. Also causes difficulty clicking SpeedDn button.
if (!document.querySelector("#chordsDiv")){
activateExtension();
} else {
console.log("YouTube Overlay deactivated");
var lyricsOnExit = chordsTA.value;
var lyricsLocOnExit = chordsTA.scrollTop;
var lyricsSizeOnExit = chordsTA.style.fontSize;
var chordsTALeftPaddingNonFSOnExit = chordsTA.style.paddingLeft; // won't be possible to save FS padding unless i deactivate extension with an X btn. Due to scope prob.
document.querySelector("#chordsDiv").remove();
clearInterval(resCheckerID);
delete window.resCheckerID;
}
This is funcsTBInsertedIntoDOM.ts:
console.log("Loading essential funcs needed for YouTube Overlay extension.")
clearInterval(asIntervalID); // cannot clear this from yt.ts because yt.ts runs in a sandbox. So need to clear it here, if it exists, on startup. Thankfully doesn't throw error even if doesn't exist.
clearTimeout(hideChordsCtrlsTimeoutID);
var asSpeeds = {1: 250, 2: 150, 3: 100, 4: 90, 5: 75, 6: 60, 7: 50, 8: 40, 9: 30};
var chordsCtrls:HTMLDivElement = document.querySelector("#chordsCtrls");
var chordsTA:HTMLTextAreaElement = document.querySelector("#chordsTA");
var asBtn:HTMLButtonElement = document.querySelector("#asBtn");
var autoScrollSpeed = 250;
var asIntervalID = 0;
function toggleAutoScroll() {
if(asIntervalID){
clearInterval(asIntervalID);
asIntervalID = 0;
console.log("Stopped autoscroll.");
document.querySelector("#speedUp").remove(); document.querySelector("#speedDn").remove();
setAttributes(asBtn, {"data-front": `Auto-Scroll Off`, 'data-back': 'Turn On'});
return;
}
// create speed + - buttons
let speedUp = document.createElement("a");
speedUp.textContent = "+";
setAttributes(speedUp, {'id': 'speedUp', 'class': 'btn noselect', 'onclick': 'speedUpAS();'});
document.querySelector("#chordsCtrls").insertBefore(speedUp,document.querySelector("#decTxtSize"));
let speedDn = document.createElement("a");
speedDn.textContent = "-";
setAttributes(speedDn, {'id': 'speedDn', 'class': 'btn noselect', 'onclick': 'speedDnAS();'});
document.querySelector("#chordsCtrls").insertBefore(speedDn,asBtn);;
setAttributes(asBtn, {"data-front": `Speed: ${getKeyByValue(asSpeeds,autoScrollSpeed)}`, 'data-back': 'Turn Off'});
asIntervalID = setInterval(_=>{chordsTA.scrollBy(0, 1)}, autoScrollSpeed);
console.log("Started autoscroll.")
}
var speedUpAS = () => {
console.log("Speeding up autoscroll")
let asBtnText = asBtn.getAttribute('data-front');
let newSpeed:number = parseInt(asBtnText.charAt(asBtnText.length - 1))+1;
if (newSpeed in asSpeeds){
clearInterval(asIntervalID);
autoScrollSpeed = asSpeeds[newSpeed];
asIntervalID = 0;
asBtn.setAttribute('data-front', `Speed: ${getKeyByValue(asSpeeds, autoScrollSpeed)}`);
asIntervalID = setInterval(_=>{chordsTA.scrollBy(0, 1)}, autoScrollSpeed);
}
}
var speedDnAS = () => {
console.log("Speeding down autoscroll")
let asBtnText = asBtn.getAttribute('data-front');
let newSpeed:number = parseInt(asBtnText.charAt(asBtnText.length - 1))-1;
if (newSpeed in asSpeeds){
clearInterval(asIntervalID);
autoScrollSpeed = asSpeeds[newSpeed];
asIntervalID = 0;
asBtn.setAttribute('data-front', `Speed: ${getKeyByValue(asSpeeds, autoScrollSpeed)}`);
asIntervalID = setInterval(_=>{chordsTA.scrollBy(0, 1)}, autoScrollSpeed);
}
}
var incTxtSize = () => {
let currFontSize = parseFloat(chordsTA.style.fontSize);
let newFontSize = currFontSize += 1;
chordsTA.style.fontSize = `${newFontSize}px`;
qickSizeUp();
}
var decTxtSize = () => {
let currFontSize = parseFloat(chordsTA.style.fontSize);
let newFontSize = currFontSize -= 1;
chordsTA.style.fontSize = `${newFontSize}px`;
qickSizeDn();
}
var unhideChordsCtrls = () => {
clearTimeout(hideChordsCtrlsTimeoutID);
let childrenOfchordsCtrlsDiv:any = chordsCtrls.getElementsByTagName("*");
for (let index = 0; index < childrenOfchordsCtrlsDiv.length; index++) {
childrenOfchordsCtrlsDiv[index].style.transform = "translate(0,0)";
}
}
var hideChordsCtrlsTimeoutID = 0;
var hideChordsCtrls = () => {
hideChordsCtrlsTimeoutID = setTimeout(() => {
let childrenOfchordsCtrlsDiv:any = chordsCtrls.getElementsByTagName("*");
for (let index = 0; index < childrenOfchordsCtrlsDiv.length; index++) {
childrenOfchordsCtrlsDiv[index].style.transform = "translate(0,-100%)";
}
}, 2000);
}
hideChordsCtrlsTimeoutID = setTimeout(() => { //hide the controls after initially showing them for 4 secs
hideChordsCtrls();
}, 4000);
var decIndent = () => {
let newLeftPadding = (parseInt(chordsTA.style.paddingLeft) - 50);
if (!newLeftPadding) newLeftPadding = 0; // this catches NaN on first run, as it is not set. Also doesn't allow to go less than 0 somehow, luckily.
chordsTA.style.paddingLeft = `${newLeftPadding}px`;
}
var incIndent = () => {
let newLeftPadding = (parseInt(chordsTA.style.paddingLeft) + 50);
if (!newLeftPadding) newLeftPadding = 50; // this catches NaN on first run, as it is not set.
if (newLeftPadding > document.querySelector("#chordsDiv").getBoundingClientRect().width) return;
chordsTA.style.paddingLeft = `${newLeftPadding}px`;
}
// following funcs stolen from SO for finding a key by its value & setting attributes multiple at a time.
function getKeyByValue(object:object, value:Number) {
return Object.keys(object).find(key => object[key] === value);
}
function setAttributes(el:HTMLElement, attrs:object) {
for(var key in attrs) {
el.setAttribute(key, attrs[key]);
}
}
Or you may prefer to read the code over at GitHub: https://github.com/XtremePwnership/YoutubeOverlay
Since YouTube video pages are hosted at youtube.com/watch, specifying that in your manifest is the way to go:
{
"name": "YouTube Overlay",
"version": "0.1",
"manifest_version" : 3,
"description": "Lays overlay on YouTube, can be used for guitar chords/lyrics.",
"background" : {
"service_worker" : "bg.js"
},
"action": {},
"permissions": ["activeTab", "scripting"],
"web_accessible_resources": [{
"resources": ["funcsTBInsertedIntoDOM.js"],
"matches": ["https://www.youtube.com/watch?v=*"]
}]
}

Why does left-padding becomes part of span?

I am creating chinese checkers, I use span tag to create circles. Added only left padding to the top corner. I have two questions:
1) Why rows seem to have distance between them, but not columns.
2) To fix 1) I added padding-left, but instead of adding distance the padding became part of the circle, why?
Here's the link how it looks:
Here's part of code:
.player0{
height: 40px;
width: 40px;
padding-right: 5px;
background-color: transparent;
border-radius: 50%;
display: inline-block;
}
divs += "<span class='player"+fullBoardArr[fullArrIter]+" 'onclick='send()'></span>"
divs += "<div class='clear_float'> </div>" //for separation of rows
As I said in comments, you need to use margin instead of padding.
I would not use "clear_float" (I assume this is about the float CSS property). Instead wrap elements that belong in the same row, in a separate div element.
From the image you included, it seems that you have a problem in aligning the cells. You can use many ways to solve this, but as your board is symmetric horizontally (ignoring the colors), you can just use text-align: center.
I had some fun in creating JavaScript logic for the board itself. You may find some aspects interesting to reuse:
class Cell {
constructor(rowId, colId) {
this._value = 0;
this.rowId = rowId;
this.colId = colId;
this.elem = document.createElement("span");
this.elem.className = "cell";
this.selected = false;
}
get value() {
return this._value;
}
set value(value) {
this._value = value;
this.elem.style.backgroundColor = ["", "grey", "blue", "red"][value];
}
toggleSelected() {
this.selected = !this.selected;
this.elem.classList.toggle("selected", this.selected);
}
}
class Board {
constructor() {
this._container = document.createElement("div");
this._container.className = "board";
this.elemMap = new Map;
this.grid = [[0,0,0,0,2,0,0,0,0,0,0,0,0],
[0,0,0,0,2,2,0,0,0,0,0,0,0],
[0,0,0,0,2,2,2,0,0,0,0,0,0],
[0,0,0,0,2,2,2,2,0,0,0,0,0],
[3,3,3,3,1,1,1,1,1,4,4,4,4],
[0,3,3,3,1,1,1,1,1,1,4,4,4],
[0,0,3,3,1,1,1,1,1,1,1,4,4],
[0,0,0,3,1,1,1,1,1,1,1,1,4],
[0,0,0,0,1,1,1,1,1,1,1,1,1]];
// create the data structure for the game and produce the corresponding DOM
this.grid.forEach((row, rowId) => {
let div = document.createElement("div");
row.forEach((value, colId) => {
if (!value--) return;
let cell = row[colId] = new Cell(rowId, colId);
cell.value = value;
div.appendChild(cell.elem);
this.elemMap.set(cell.elem, cell);
});
this._container.appendChild(div);
});
}
set container(elem) {
elem.appendChild(this._container);
}
getEventCell(e) {
return this.elemMap.get(e.target);
}
set selected(cell) {
if (this._selected) {
this._selected.toggleSelected();
this._selected = null;
}
if (!cell) return;
cell.toggleSelected();
this._selected = cell;
}
get selected() {
return this._selected;
}
move(cellFrom, cellTo) {
// TODO: Implement the real move rules here
if (!cellFrom.value) return; // must move a piece
if (cellTo.value) return; // capturing not allowed
cellTo.value = cellFrom.value;
cellFrom.value = 0;
board.selected = null;
}
}
let container = document.querySelector("#container");
let board = new Board();
board.container = container;
container.addEventListener("click", e => {
let cell = board.getEventCell(e);
if (!cell) return; // click was not on a cell
if (!board.selected || cell.value) {
board.selected = cell;
} else {
board.move(board.selected, cell);
}
});
.board {
text-align: center;
margin-left: auto; margin-right: auto;
}
.cell {
height: 15px;
width: 15px;
margin: 0px 2px;
border: 1px solid;
border-radius: 50%;
display: inline-block;
}
.selected {
border-color: orange;
}
<div id="container"></div>
You can click to select a piece and then click again on an empty spot to move it there.
Use margin instead of padding:
.player0{
height: 40px;
width: 40px;
margin-right: 5px;
background-color: transparent;
border-radius: 50%;
display: inline-block;
}
As an easy-to-remember quick reference, margin changes the position starting from outside the element border, padding from the inside

How to link two if statements

I am learning JS and have created a carousel with a caption underneath.
How do I get the Prev/Next buttons to affect the caption as well as the image? I've tried combining the if statements in several ways but have failed miserably.
Relevant HTML:
<span id="prev" class="arrow">❮</span>
<div class="karussell" id="karussell">
<img class="karu" name="esislaid">
</div>
<span id="next" class="arrow">❯</span>
<div class="caption">
<h3 id="esikiri"></h3>
</div>
JS:
var p = 0;
var s = 0;
var esileht = [];
var aeg = 5000;
var kiri = [];
//Image List
esileht[0] = 'img/tooted/raamat/graafvanalinn2016.jpg';
esileht[1] = 'img/tooted/kaart/kaart_taskus_esipool.jpg';
esileht[2] = 'img/tooted/kaart/graafkaart_esikylg.jpg';
//Captions
kiri[0] = 'Raamat "Tallinn. Graafiline vanalinn"';
kiri[1] = 'Tallinna vanalinna graafiline kaart (suur formaat)';
kiri[2] = 'Tallinna vanalinna graafiline kaart (väike formaat)';
// Left and Right arrows
//Eelmine
function eelmine(){
if (p === 0){
p = esileht.length;
}
p = p - 1;
return esileht[p];
}
//Jargmine
function jargmine(){
p = p + 1;
p = p % esileht.length;
return esileht[p];
}
document.getElementById('prev').addEventListener('click', function (e){
document.querySelector('#karussell img').src = eelmine();
}
);
document.getElementById('next').addEventListener('click', function (e) {
document.querySelector('#karussell img').src = jargmine();
}
);
//Change Image
function changePilt (){
document.esislaid.src = esileht[p];
if(p < esileht.length -1){
p++;
} else {
p = 0;
}
setTimeout("changePilt()", aeg);
}
//Change Caption
function changeKiri(){
document.getElementById('esikiri').innerHTML = kiri[s];
if(s < kiri.length - 1){
s++;
}
else {
s = 0;
}
setTimeout('changeKiri()', aeg);
}
document.body.onload = function(){
changePilt();
changeKiri();
};
CSS, just in case:
.karussell {
position: relative;
width: 100%;
max-height: 600px;
overflow: hidden;
}
.arrow {
cursor: pointer;
position: absolute;
top: 40%;
width: auto;
color: #00A7E0;
padding: 16px;
font-weight: bold;
font-size: 18px;
border-radius: 3px;
transition: 0.6s ease;
}
#next {
right: 0;
}
#prev {
left: 0;
}
.arrow:hover {
background-color: rgba(0,0,0,0.8);
}
.caption {
text-align: center;
color: #00A7E0;
padding: 2px 16px;
}
.karu {
max-width: 75%;
animation-name: fade;
animation-duration: 2s;
}
#keyframes fade {
from {opacity: 0.4}
to {opacity: 1}
}
#media (max-width:767px){.karu{max-width: 95%;}}
I made a fiddle to try to illustrate (had to paste the js into the html tab to gt it to work for some reason): Fiddle
Really you just need to use the .innerHTML() feature and do exactly what you already have. Either create a eelmine2() function (or something like that) and call it again, grabbing the content from kiri[] or instead just return the p and use it in two places:
document.getElementById('prev').addEventListener('click', function (e){
document.querySelector('#karussell img').src = eelmine();
document.querySelector('#esikiri').innerHTML = eelmine2();
});
function eelmine2(){
if (p === 0){
p = kiri.length;
}
p = p - 1;
return kiri[p];
}
or
document.getElementById('prev').addEventListener('click', function (e){
var change = eelmine();
document.querySelector('#karussell img').src = esileht[change];
document.querySelector('#esikiri').innerHTML = kiri[change];
});
function eelmine(){
if (p === 0){
p = kiri.length;
}
p = p - 1;
return p;
}
This assumes your code is using the same global vars inside public functions that you have set up in your Fiddle. You should fix that to have variables passed into the functions before going live with all of this, but I'm not addressing that any further here.

Categories

Resources