I havet this codepen: https://codepen.io/sp2012/pen/VwpyWdp . Unfortunately, this code is too advanced for me. The game has three maps. I want to keep only the first map and when the game is finished, if you click on the map the game restarts.
The code follows:
This is the HTML:
<div id="game-container-1" class="game-container">
<div id="map-and-controls">
<div id="game-map-1" class="game-map">
<div id="tiles" class="layer"></div>
<div id="sprites" class="layer"></div>
<div id="success-msg">Goal reached! Tap the maze to change levels.</div>
</div>
<!-- controls-->
<div id="controls">
<button id="up"></button>
<div id="horiz">
<button id="left"></button>
<button id="right"></button>
</div>
<button id="down"></button>
</div>
</div>
<p id="text-1" class="text">Use cursor keys or buttons to move the marble.</p>
</div>
This is the CSS:
/*
* General Styling
*/
body {
font-family: Calibri;
transition: 0.2s ease;
text-align: center;
}
body.success {
background-color: #b7f0b7;
transition: 0.2s ease;
}
/* center everything in game container */
.game-container {
margin: 0px auto;
}
/*
* Map screen
*/
.game-map {
position: relative;
}
/*
* Output text styles
*/
p {
margin: 10px 0px;
padding: 0px;
}
/*
* Map on left, controls on right
* Adapted for the mobile Medium app
*/
#map-and-controls {
display: flex;
justify-content: center;
}
/*
* Controls
*/
#controls {
margin-left: 10px;
text-align: center;
}
/*
* Container for right and left buttons
*/
#controls #horiz {
display: flex;
align-items: center;
justify-content: center;
}
/*
* General button styles
*/
#controls button {
padding: 10px 10px;
margin-top: 10px;
background-color: #DDD;
border: 1px solid #000;
width: 38px;
height: 38px;
border-radius: 3px;
cursor: pointer;
position: relative;
}
/*
* Spacing between horiz buttons
*/
button#right {
margin-left: 5px;
}
button#left {
margin-right: 5px;
}
/*
* General button arrow styles
*/
#controls button::before {
content:'';
width: 0px;
position: absolute;
}
/*
* Specific Arrow Styles
*/
button#left::before {
border-top: 10px solid transparent;
border-right: 15px solid #000;
border-bottom: 10px solid transparent;
left: 10px;
top: 9px;
}
button#right::before {
border-top: 10px solid transparent;
border-left: 15px solid #000;
border-bottom: 10px solid transparent;
left: 12px;
top: 9px;
}
button#up::before {
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-bottom: 15px solid #000;
left: 9px;
top: 9px;
}
button#down::before {
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-top: 15px solid #000;
left: 9px;
top: 12px;
}
#success-msg {
opacity: 0;
transition: opacity 0.2s ease;
position: absolute;
padding: 5px 5px;
background-color: rgba(0,0,0,0.5);
color: white;
width: calc(100% - 8px);
}
body.success #success-msg {
opacity: 1;
transition: opacity 0.2 ease;
}
/*
* Layers and tiles are positioned absolutely
* within coordinate system of .game-map
*/
div.layer,
div.layer div {
position: absolute;
}
/* border for floors and wall */
#tiles div {
border: 1px solid grey;
}
/*
* Default wall and floor styles
*/
.default .floor {
background-color: lightgrey;
}
.default .wall {
background-color: skyblue;
}
/*
* grassland theme
*/
.grassland .floor {
background-color: #7bb76d;
}
.grassland .wall {
background-color: #806d51;
}
.grassland #player {
background-color: #b2ccec;
}
/*
* dungeon theme
*/
.dungeon .floor {
background-color: darkgrey;
}
.dungeon .wall {
background-color: #9c649c;
}
.dungeon #player {
background-color: #ab1431;
}
/*
* player and goal are slightly smaller than tiles
*/
.player,
.goal {
transform-origin: center;
transform:scale(0.85);
}
/*
* Goal colors
*/
.goal {
background-color: #FFD700;
border: 1px solid #98720b;
}
/*
* Player default colors
*/
.player {
background-color: #90ee90;
border: 1px solid #008000;
transition: left 0.2s ease, top 0.2s ease;
}
/*
* Player wobbles when colliding with wall or border
*/
.player.collide {
animation: wobble 0.5s;
animation-iteration-count: infinite;
transition: background-color 0.2s;
}
/*
* Wobble animation
*/
#keyframes wobble {
0% { transform: scale(0.85) translate(1px, 1px); }
10% { transform: scale(0.85) translate(-1px, -2px); }
20% { transform: scale(0.85) translate(-3px, 0px); }
30% { transform: scale(0.85) translate(3px, 2px); }
40% { transform: scale(0.85) translate(1px, -1px);}
50% { transform: scale(0.85) translate(-1px, 2px); }
60% { transform: scale(0.85) translate(-3px, 1px); }
70% { transform: scale(0.85) translate(3px, 1px); }
80% { transform: scale(0.85) translate(-1px, -1px); }
90% { transform: scale(0.85) translate(1px, 2px); }
100% { transform: scale(0.85) translate(1px, -2px);; }
}
Here is the JavaScript:
let app = {};
(function(context) {
/*
* Build an array of levels.
* This will scale better if it is stored in a separate JSON File.
*/
let levels = [];
levels[0] = {
map:[
[1,1,0,0,1],
[1,0,0,0,0],
[0,0,1,1,0],
[0,0,0,1,0],
[0,1,0,1,0]
],
player:{
x:0,
y:4
},
goal:{
x:4,
y:1
},
theme:'default',
};
// second level
levels[1] = {
map:[
[1,0,1,1,1,1],
[0,0,0,0,0,0],
[0,1,1,1,0,0],
[0,0,0,1,1,0],
[0,1,0,1,0,0]
],
theme:'grassland',
player:{
x:2,
y:4
},
goal:{
x:4,
y:4
}
};
// third level
levels[2] = {
map:[
[1,0,1,0,0,1,0],
[0,0,0,0,0,1,0],
[1,0,1,1,0,0,0],
[1,0,0,1,0,1,0],
[1,1,0,0,1,0,0]
],
theme:'dungeon',
player:{
x:2,
y:4
},
goal:{
x:6,
y:4
}
};
/*
* The game object constructor.
* #param {String} id - the id of the game container DOM element.
* #param {Object} level - the starting level of the game.
*/
function Game(id,level) {
this.el = document.getElementById(id);
// level addition
this.level_idx = 0;
// establish the basic properties common to all this objects.
this.tileTypes = ['floor','wall'];
this.tileDim = 32;
// inherit the level's properties: map, player start, goal start.
this.map = level.map;
// level switch
this.theme = level.theme;
// make a copy of the level's player.
this.player = {...level.player};
// create a property for the DOM element, to be set later.
this.player.el = null;
// make a copy of the goal.
this.goal = {...level.goal};
}
/*
* Create a tile or sprite <div> element.
* #param {Number} x - the horizontal coordinate the 2D array.
* #param {Number} y - the vertical coordinate in the 2D array.
*/
Game.prototype.createEl = function(x,y,type) {
// create one tile.
let el = document.createElement('div');
// two class names: one for tile, one or the tile type.
el.className = type;
// set width and height of tile based on the passed-in dimensions.
el.style.width = el.style.height = this.tileDim + 'px';
// set left positions based on x coordinate.
el.style.left = x*this.tileDim + 'px';
// set top position based on y coordinate.
el.style.top = y*this.tileDim + 'px';
return el;
}
/*
* Applies the level theme as a class to the game element.
* Populates the map by adding tiles and sprites to their respective layers.
*/
Game.prototype.populateMap = function() {
// add theme call
this.el.className = 'game-container ' + this.theme;
// make a reference to the tiles layer in the DOM.
let tiles = this.el.querySelector('#tiles');
// set up our loop to populate the grid.
for (var y = 0; y < this.map.length; ++y) {
for (var x = 0; x < this.map[y].length; ++x) {
let tileCode = this.map[y][x];
// determine tile type using code
// index into the tileTypes array using the code.
let tileType = this.tileTypes[tileCode];
// call the helper function
let tile = this.createEl(x,y,tileType);
// add to layer
tiles.appendChild(tile);
}
}
}
/*
* Place the player or goal sprite.
* #param {String} type - either 'player' or 'goal', used by createEl and becomes DOM ID
*/
Game.prototype.placeSprite = function(type) {
// syntactic sugar
let x = this[type].x
let y = this[type].y;
// reuse the createTile function
let sprite = this.createEl(x,y,type);
sprite.id = type;
// set the border radius of the sprite.
sprite.style.borderRadius = this.tileDim + 'px';
// get half the difference between tile and sprite.
// grab the layer
let layer = this.el.querySelector('#sprites');
layer.appendChild(sprite);
return sprite;
}
/*
* Triggers a collide animation on the player sprite.
*/
Game.prototype.collide = function() {
this.player.el.className += ' collide';
let obj = this;
window.setTimeout(function() {
obj.player.el.className = 'player';
},200);
return 0;
};
/*
* Moves the player sprite left.
*/
Game.prototype.moveLeft = function() {
// if at the boundary, return
if (this.player.x == 0) {
this.collide();
return;
}
// itentify next tile
let nextTile = this.map[this.player.y][this.player.x-1];
// if next tile is a wall, add collide effect and return
if (nextTile ==1) {
this.collide();
return;
}
// change coordinates of player object
this.player.x -=1;
// update location of DOM element
this.updateHoriz();
};
/*
* Moves the player sprite up.
*/
Game.prototype.moveUp = function() {
if (this.player.y == 0) {
// at end: these could be combined
this.collide();
return;
}
let nextTile = this.map[this.player.y-1][this.player.x];
if (nextTile ==1) {
this.collide();
return;
}
this.player.y -=1;
this.updateVert();
};
/*
* Moves the player sprite right.
*/
Game.prototype.moveRight = function() {
if (this.player.x == this.map[this.player.y].length-1) {
this.collide();
return;
}
nextTile = this.map[this.player.y][this.player.x+1];
if (nextTile ==1) {
this.collide()
return;
}
this.player.x += 1;
this.updateHoriz();
};
/*
* Moves player sprite down.
*/
Game.prototype.moveDown = function() {
if (this.player.y == this.map.length-1) {
this.collide();
return;
}
// find the next tile in the 2D array.
let nextTile = this.map[this.player.y+1][this.player.x];
if (nextTile ==1) {
this.collide()
return;
}
this.player.y += 1;
this.updateVert();
};
/*
* Updates vertical position of player sprite based on object's y coordinates.
*/
Game.prototype.updateVert = function() {
this.player.el.style.top = this.player.y * this.tileDim+ 'px';
};
/*
* Updates horizontal position of player sprite based on object's x coordinates.
*/
Game.prototype.updateHoriz = function() {
this.player.el.style.left = this.player.x * this.tileDim + 'px';
};
/*
* Moves player based on keyboard cursor presses.
*/
Game.prototype.movePlayer = function(event) {
event.preventDefault();
if (event.keyCode < 37 || event.keyCode > 40) {
return;
}
switch (event.keyCode) {
case 37:
this.moveLeft();
break;
case 38:
this.moveUp();
break;
case 39:
this.moveRight();
break;
case 40:
this.moveDown();
break;
}
}
/*
* Check on whether goal has been reached.
*/
Game.prototype.checkGoal = function() {
let body = document.querySelector('body');
if (this.player.y == this.goal.y &&
this.player.x == this.goal.x) {
body.className = 'success';
}
else {
body.className = '';
}
}
/*
* Changes the level of the game object.
*/
Game.prototype.changeLevel = function() {
// update the level index.
this.level_idx ++;
// if higher than max index, set back to zero.
if (this.level_idx > levels.length -1) {
this.level_idx = 0;
}
// get the level at this index.
let level = levels[this.level_idx];
// sync the map with the level map.
this.map = level.map;
// sync the theme with the level theme.
this.theme = level.theme;
// make a copy of the level's player object, since x and y change during the game.
this.player = {...level.player};
// make a copy of the level's goal object, since x and y change between levels.
this.goal = {...level.goal};
}
/*
* If goal has been reached,
*/
Game.prototype.addMazeListener = function() {
// grab the map
let map = this.el.querySelector('.game-map');
// grab reference to game object since we are going into a function
// and "this" will no longer refer to the game object
let obj = this;
// if game board is clicked or tapped, see if we should change levels
map.addEventListener('mousedown',function(e) {
// if not at the goal, then get outta here
if (obj.player.y != obj.goal.y ||
obj.player.x != obj.goal.x) {
return;
}
// change level of game object by changing it's properties
obj.changeLevel();
// get the two layers
let layers = obj.el.querySelectorAll('.layer');
// clear tiles and sprites from layers
for (layer of layers) {
layer.innerHTML = '';
}
// place the new level.
obj.placeLevel();
// check the goal to reset the message.
obj.checkGoal();
});
};
/*
* Responds to a keydown event by moving the player and checking the goal.
*/
Game.prototype.keyboardListener = function() {
document.addEventListener('keydown', event => {
this.movePlayer(event);
this.checkGoal();
});
}
/*
* Adds mouse down listeners to buttons
*/
Game.prototype.buttonListeners = function() {
let up = document.getElementById('up');
let left = document.getElementById('left');
let down = document.getElementById('down')
let right = document.getElementById('right');
// the sprite is out of date
let obj = this;
up.addEventListener('mousedown',function() {
obj.moveUp();
obj.checkGoal();
});
down.addEventListener('mousedown',function() {
obj.moveDown();
obj.checkGoal();
});
left.addEventListener('mousedown',function() {
obj.moveLeft();
obj.checkGoal();
});
right.addEventListener('mousedown',function() {
obj.moveRight();
obj.checkGoal();
});
}
/*
* Sets the message of the text element.
* #param {String} msg - The message to be printed.
*/
Game.prototype.setMessage = function(msg) {
let text_el = this.el.querySelector('.text');
text_el.textContent = msg;
};
/*
* Sizes up the map based on array dimensions.
*/
Game.prototype.sizeUp = function() {
// inner container so that text can be below it
let map = this.el.querySelector('.game-map');
// inner container, height. Need this.map
map.style.height = this.map.length * this.tileDim + 'px';
map.style.width = this.map[0].length * this.tileDim + 'px';
};
/*
* Populates the map.
* Sizes up the map based on array dimensions.
* Gives the goal and player some references.
*/
Game.prototype.placeLevel = function() {
this.populateMap();
this.sizeUp();
this.placeSprite('goal');
// we want the DOM element that gets returned...
let playerSprite = this.placeSprite('player');
// ..so we can store it in the playerSprite element.
this.player.el = playerSprite;
}
/*
* Add keyboard, button, and maze tap listeners
*/
Game.prototype.addListeners = function() {
this.keyboardListener();
this.buttonListeners();
// changing levels
this.addMazeListener();
}
/*
* Initialization function called once
*/
context.init = function () {
let myGame = new Game('game-container-1',levels[0]);
// encapsulate for multi-level
myGame.placeLevel();
// add listeners
myGame.addListeners();
}
})(app);
/*
* Tell app to activate the init() function.
*/
app.init();
Any ideas please?
Just comment out the second and third level section of the levels[0] object (maps). Change the HTML content that makes reference to other levels.
let app = {};
(function(context) {
/*
* Build an array of levels.
* This will scale better if it is stored in a separate JSON File.
*/
let levels = [];
levels[0] = {
map:[
[0,0,1,1,1,0,1,1,0],
[1,0,1,1,0,0,0,0,0],
[0,0,1,1,0,1,1,0,1],
[1,0,0,0,0,0,1,0,1],
[1,1,1,1,1,0,1,0,1]
],
player:{
x:0,
y:0
},
goal:{
x:7,
y:4
},
theme:'default',
};
/* second level
levels[1] = {
map:[
[1,0,1,1,1,1],
[0,0,0,0,0,0],
[0,1,1,1,0,0],
[0,0,0,1,1,0],
[0,1,0,1,0,0]
],
theme:'grassland',
player:{
x:2,
y:4
},
goal:{
x:4,
y:4
}
};
// third level
levels[2] = {
map:[
[1,0,1,0,0,1,0],
[0,0,0,0,0,1,0],
[1,0,1,1,0,0,0],
[1,0,0,1,0,1,0],
[1,1,0,0,1,0,0]
],
theme:'dungeon',
player:{
x:2,
y:4
},
goal:{
x:6,
y:4
}
};
/*
* The game object constructor.
* #param {String} id - the id of the game container DOM element.
* #param {Object} level - the starting level of the game.
*/
function Game(id,level) {
this.el = document.getElementById(id);
// level addition
this.level_idx = 0;
// establish the basic properties common to all this objects.
this.tileTypes = ['floor','wall'];
this.tileDim = 32;
// inherit the level's properties: map, player start, goal start.
this.map = level.map;
// level switch
this.theme = level.theme;
// make a copy of the level's player.
this.player = {...level.player};
// create a property for the DOM element, to be set later.
this.player.el = null;
// make a copy of the goal.
this.goal = {...level.goal};
}
/*
* Create a tile or sprite <div> element.
* #param {Number} x - the horizontal coordinate the 2D array.
* #param {Number} y - the vertical coordinate in the 2D array.
*/
Game.prototype.createEl = function(x,y,type) {
// create one tile.
let el = document.createElement('div');
// two class names: one for tile, one or the tile type.
el.className = type;
// set width and height of tile based on the passed-in dimensions.
el.style.width = el.style.height = this.tileDim + 'px';
// set left positions based on x coordinate.
el.style.left = x*this.tileDim + 'px';
// set top position based on y coordinate.
el.style.top = y*this.tileDim + 'px';
return el;
}
/*
* Applies the level theme as a class to the game element.
* Populates the map by adding tiles and sprites to their respective layers.
*/
Game.prototype.populateMap = function() {
// add theme call
this.el.className = 'game-container ' + this.theme;
// make a reference to the tiles layer in the DOM.
let tiles = this.el.querySelector('#tiles');
// set up our loop to populate the grid.
for (var y = 0; y < this.map.length; ++y) {
for (var x = 0; x < this.map[y].length; ++x) {
let tileCode = this.map[y][x];
// determine tile type using code
// index into the tileTypes array using the code.
let tileType = this.tileTypes[tileCode];
// call the helper function
let tile = this.createEl(x,y,tileType);
// add to layer
tiles.appendChild(tile);
}
}
}
/*
* Place the player or goal sprite.
* #param {String} type - either 'player' or 'goal', used by createEl and becomes DOM ID
*/
Game.prototype.placeSprite = function(type) {
// syntactic sugar
let x = this[type].x
let y = this[type].y;
// reuse the createTile function
let sprite = this.createEl(x,y,type);
sprite.id = type;
// set the border radius of the sprite.
sprite.style.borderRadius = this.tileDim + 'px';
// get half the difference between tile and sprite.
// grab the layer
let layer = this.el.querySelector('#sprites');
layer.appendChild(sprite);
return sprite;
}
/*
* Triggers a collide animation on the player sprite.
*/
Game.prototype.collide = function() {
this.player.el.className += ' collide';
let obj = this;
window.setTimeout(function() {
obj.player.el.className = 'player';
},200);
return 0;
};
/*
* Moves the player sprite left.
*/
Game.prototype.moveLeft = function() {
// if at the boundary, return
if (this.player.x == 0) {
this.collide();
return;
}
// itentify next tile
let nextTile = this.map[this.player.y][this.player.x-1];
// if next tile is a wall, add collide effect and return
if (nextTile ==1) {
this.collide();
return;
}
// change coordinates of player object
this.player.x -=1;
// update location of DOM element
this.updateHoriz();
};
/*
* Moves the player sprite up.
*/
Game.prototype.moveUp = function() {
if (this.player.y == 0) {
// at end: these could be combined
this.collide();
return;
}
let nextTile = this.map[this.player.y-1][this.player.x];
if (nextTile ==1) {
this.collide();
return;
}
this.player.y -=1;
this.updateVert();
};
/*
* Moves the player sprite right.
*/
Game.prototype.moveRight = function() {
if (this.player.x == this.map[this.player.y].length-1) {
this.collide();
return;
}
nextTile = this.map[this.player.y][this.player.x+1];
if (nextTile ==1) {
this.collide()
return;
}
this.player.x += 1;
this.updateHoriz();
};
/*
* Moves player sprite down.
*/
Game.prototype.moveDown = function() {
if (this.player.y == this.map.length-1) {
this.collide();
return;
}
// find the next tile in the 2D array.
let nextTile = this.map[this.player.y+1][this.player.x];
if (nextTile ==1) {
this.collide()
return;
}
this.player.y += 1;
this.updateVert();
};
/*
* Updates vertical position of player sprite based on object's y coordinates.
*/
Game.prototype.updateVert = function() {
this.player.el.style.top = this.player.y * this.tileDim+ 'px';
};
/*
* Updates horizontal position of player sprite based on object's x coordinates.
*/
Game.prototype.updateHoriz = function() {
this.player.el.style.left = this.player.x * this.tileDim + 'px';
};
/*
* Moves player based on keyboard cursor presses.
*/
Game.prototype.movePlayer = function(event) {
event.preventDefault();
if (event.keyCode < 37 || event.keyCode > 40) {
return;
}
switch (event.keyCode) {
case 37:
this.moveLeft();
break;
case 38:
this.moveUp();
break;
case 39:
this.moveRight();
break;
case 40:
this.moveDown();
break;
}
}
/*
* Check on whether goal has been reached.
*/
Game.prototype.checkGoal = function() {
let body = document.querySelector('body');
if (this.player.y == this.goal.y &&
this.player.x == this.goal.x) {
body.className = 'success';
}
else {
body.className = '';
}
}
/*
* Changes the level of the game object.
*/
Game.prototype.changeLevel = function() {
// update the level index.
this.level_idx ++;
// if higher than max index, set back to zero.
if (this.level_idx > levels.length -1) {
this.level_idx = 0;
}
// get the level at this index.
let level = levels[this.level_idx];
// sync the map with the level map.
this.map = level.map;
// sync the theme with the level theme.
this.theme = level.theme;
// make a copy of the level's player object, since x and y change during the game.
this.player = {...level.player};
// make a copy of the level's goal object, since x and y change between levels.
this.goal = {...level.goal};
}
/*
* If goal has been reached,
*/
Game.prototype.addMazeListener = function() {
// grab the map
let map = this.el.querySelector('.game-map');
// grab reference to game object since we are going into a function
// and "this" will no longer refer to the game object
let obj = this;
// if game board is clicked or tapped, see if we should change levels
map.addEventListener('mousedown',function(e) {
// if not at the goal, then get outta here
if (obj.player.y != obj.goal.y ||
obj.player.x != obj.goal.x) {
return;
}
// change level of game object by changing it's properties
obj.changeLevel();
// get the two layers
let layers = obj.el.querySelectorAll('.layer');
// clear tiles and sprites from layers
for (layer of layers) {
layer.innerHTML = '';
}
// place the new level.
obj.placeLevel();
// check the goal to reset the message.
obj.checkGoal();
});
};
/*
* Responds to a keydown event by moving the player and checking the goal.
*/
Game.prototype.keyboardListener = function() {
document.addEventListener('keydown', event => {
this.movePlayer(event);
this.checkGoal();
});
}
/*
* Adds mouse down listeners to buttons
*/
Game.prototype.buttonListeners = function() {
let up = document.getElementById('up');
let left = document.getElementById('left');
let down = document.getElementById('down')
let right = document.getElementById('right');
// the sprite is out of date
let obj = this;
up.addEventListener('mousedown',function() {
obj.moveUp();
obj.checkGoal();
});
down.addEventListener('mousedown',function() {
obj.moveDown();
obj.checkGoal();
});
left.addEventListener('mousedown',function() {
obj.moveLeft();
obj.checkGoal();
});
right.addEventListener('mousedown',function() {
obj.moveRight();
obj.checkGoal();
});
}
/*
* Sets the message of the text element.
* #param {String} msg - The message to be printed.
*/
Game.prototype.setMessage = function(msg) {
let text_el = this.el.querySelector('.text');
text_el.textContent = msg;
};
/*
* Sizes up the map based on array dimensions.
*/
Game.prototype.sizeUp = function() {
// inner container so that text can be below it
let map = this.el.querySelector('.game-map');
// inner container, height. Need this.map
map.style.height = this.map.length * this.tileDim + 'px';
map.style.width = this.map[0].length * this.tileDim + 'px';
};
/*
* Populates the map.
* Sizes up the map based on array dimensions.
* Gives the goal and player some references.
*/
Game.prototype.placeLevel = function() {
this.populateMap();
this.sizeUp();
this.placeSprite('goal');
// we want the DOM element that gets returned...
let playerSprite = this.placeSprite('player');
// ..so we can store it in the playerSprite element.
this.player.el = playerSprite;
}
/*
* Add keyboard, button, and maze tap listeners
*/
Game.prototype.addListeners = function() {
this.keyboardListener();
this.buttonListeners();
// changing levels
this.addMazeListener();
}
/*
* Initialization function called once
*/
context.init = function () {
let myGame = new Game('game-container-1',levels[0]);
// encapsulate for multi-level
myGame.placeLevel();
// add listeners
myGame.addListeners();
}
})(app);
/*
* Tell app to activate the init() function.
*/
app.init();
/*
* General Styling
*/
body {
font-family: Calibri;
transition: 0.2s ease;
text-align: center;
}
body.success {
background-color: #b7f0b7;
transition: 0.2s ease;
}
/* center everything in game container */
.game-container {
margin: 0px auto;
}
/*
* Map screen
*/
.game-map {
position: relative;
}
/*
* Output text styles
*/
p {
margin: 10px 0px;
padding: 0px;
}
/*
* Map on left, controls on right
* Adapted for the mobile Medium app
*/
#map-and-controls {
display: flex;
justify-content: center;
}
/*
* Controls
*/
#controls {
margin-left: 10px;
text-align: center;
}
/*
* Container for right and left buttons
*/
#controls #horiz {
display: flex;
align-items: center;
justify-content: center;
}
/*
* General button styles
*/
#controls button {
padding: 10px 10px;
margin-top: 10px;
background-color: #DDD;
border: 1px solid #000;
width: 38px;
height: 38px;
border-radius: 3px;
cursor: pointer;
position: relative;
}
/*
* Spacing between horiz buttons
*/
button#right {
margin-left: 5px;
}
button#left {
margin-right: 5px;
}
/*
* General button arrow styles
*/
#controls button::before {
content:'';
width: 0px;
position: absolute;
}
/*
* Specific Arrow Styles
*/
button#left::before {
border-top: 10px solid transparent;
border-right: 15px solid #000;
border-bottom: 10px solid transparent;
left: 10px;
top: 9px;
}
button#right::before {
border-top: 10px solid transparent;
border-left: 15px solid #000;
border-bottom: 10px solid transparent;
left: 12px;
top: 9px;
}
button#up::before {
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-bottom: 15px solid #000;
left: 9px;
top: 9px;
}
button#down::before {
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-top: 15px solid #000;
left: 9px;
top: 12px;
}
#success-msg {
opacity: 0;
transition: opacity 0.2s ease;
position: absolute;
padding: 5px 5px;
background-color: rgba(0,0,0,0.5);
color: white;
width: calc(100% - 8px);
}
body.success #success-msg {
opacity: 1;
transition: opacity 0.2 ease;
}
/*
* Layers and tiles are positioned absolutely
* within coordinate system of .game-map
*/
div.layer,
div.layer div {
position: absolute;
}
/* border for floors and wall */
#tiles div {
border: 1px solid grey;
}
/*
* Default wall and floor styles
*/
.default .floor {
background-color: lightgrey;
}
.default .wall {
background-color: skyblue;
}
/*
* grassland theme
*/
.grassland .floor {
background-color: #7bb76d;
}
.grassland .wall {
background-color: #806d51;
}
.grassland #player {
background-color: #b2ccec;
}
/*
* dungeon theme
*/
.dungeon .floor {
background-color: darkgrey;
}
.dungeon .wall {
background-color: #9c649c;
}
.dungeon #player {
background-color: #ab1431;
}
/*
* player and goal are slightly smaller than tiles
*/
.player,
.goal {
transform-origin: center;
transform:scale(0.85);
}
/*
* Goal colors
*/
.goal {
background-color: #FFD700;
border: 1px solid #98720b;
}
/*
* Player default colors
*/
.player {
background-color: #90ee90;
border: 1px solid #008000;
transition: left 0.2s ease, top 0.2s ease;
}
/*
* Player wobbles when colliding with wall or border
*/
.player.collide {
animation: wobble 0.5s;
animation-iteration-count: infinite;
transition: background-color 0.2s;
}
/*
* Wobble animation
*/
#keyframes wobble {
0% { transform: scale(0.85) translate(1px, 1px); }
10% { transform: scale(0.85) translate(-1px, -2px); }
20% { transform: scale(0.85) translate(-3px, 0px); }
30% { transform: scale(0.85) translate(3px, 2px); }
40% { transform: scale(0.85) translate(1px, -1px);}
50% { transform: scale(0.85) translate(-1px, 2px); }
60% { transform: scale(0.85) translate(-3px, 1px); }
70% { transform: scale(0.85) translate(3px, 1px); }
80% { transform: scale(0.85) translate(-1px, -1px); }
90% { transform: scale(0.85) translate(1px, 2px); }
100% { transform: scale(0.85) translate(1px, -2px);; }
}
<div id="game-container-1" class="game-container">
<div id="map-and-controls">
<div id="game-map-1" class="game-map">
<div id="tiles" class="layer"></div>
<div id="sprites" class="layer"></div>
<div id="success-msg">Goal reached! Tap the maze to play again.</div>
</div>
<!-- controls-->
<div id="controls">
<button id="up"></button>
<div id="horiz">
<button id="left"></button>
<button id="right"></button>
</div>
<button id="down"></button>
</div>
</div>
<p id="text-1" class="text">Use cursor keys or buttons to move the marble.</p>
</div>
So I tried to make it so that whenever an image is clicked it will draw a circle on a canvas. The image and the canvas are suppose to overlay. However, I have a problem when I have
cnvs.style.position = 'absolute'; active all of my canvas' are stacked on each other on the first image. So if I were to click other images the circle would be drawn on the first image but not on the image clicked. However, if I comment out cnvs.style.position = 'absolute'; the canvas is being connected to the bottom of the image instead of being overlaid. I need to make it so that each canvas and image are overlaid so that when one image is clicked a circle will appear. I'm thinking I have a css problem, but I'm not sure how to fix it.
document.body.onload = addElement;
function addElement() {
// image path
const imagePath = ['https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fen%2F8%2F84%2FAssociation_of_Gay_and_Lesbian_Psychiatrists_logo.jpg&f=1&nofb=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic01.nyt.com%2Fnewsgraphics%2F2016%2F07%2F14%2Fpluto-one-year%2Fassets%2Ficon-pluto.png&f=1&nofb=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse4.mm.bing.net%2Fth%3Fid%3DOIP.oFxADNN67dYP-ke5xg7HbQHaHG%26pid%3DApi&f=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.glassdoor.com%2Fsqll%2F1065746%2Felevation-church-squarelogo-1453223965790.png&f=1&nofb=1'];
for (const image of imagePath) {
// get the item id of an image
var slice = image.slice(26, 34);
var id = image;
var hdnName = document.getElementById("sendServ");
const img = document.createElement("img");
img.src = image;
img.classList.add("new");
img.id = slice;
const cnvs = document.createElement("canvas");
cnvs.classList.add("suiteiCanvas");
// cnvs.style.position = 'absolute';
cnvs.style.left = img.offsetLeft + "px";
cnvs.style.top = img.offsetTop + "px";
cnvs.style.display = 'none';
var ctx = cnvs.getContext("2d");
ctx.clearRect(0, 0, cnvs.width, cnvs.height);
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI, false);
ctx.lineWidth = 15;
ctx.strokeStyle = '#FF0000';
ctx.stroke();
var div = document.createElement("div");
var div1 = document.createElement("div");
div.id = id;
div1.id = '1';
div.classList.add("image");
img.onclick = function draw() {
cnvs.style.display = '';
hdnName.value = img.id;
};
cnvs.onclick = function remove() {
cnvs.style.display = 'none';
hdnName.value = null;
};
document.getElementById('suitei-slider').appendChild(div);
document.getElementById(image).appendChild(img);
document.getElementById(image).appendChild(cnvs);
}
}
// slick slider
canvas.suiteiCanvas{
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
margin-left: 100px;
margin-right: 100px;
border:3px solid rgb(20, 11, 11);
}
#draw-btn {
font-size: 14px;
padding: 2px 16px 3px 16px;
margin-bottom: 8px;
}
img.new {
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
margin-left: 100px;
margin-right: 100px;
border:3px solid rgb(20, 11, 11);
}
<div class="multiple-items" id="suitei-slider"></div>
<input type="hidden" id="sendServ">
You need to set your canvases in position: absolute inside a container in position: relative so that your canvases are still contained in the container. Since the containers are not in position: absolute, they don't overlay, but their content will, so your canvases will overlay with the images.
Then, you have to center you canvases (I suspect), so I set the canvases dimensions (for now it's hard coded) and fixed the x position of the circle.
I hope it is what you were looking for.
document.body.onload = addElement;
function addElement() {
// image path
const imagePath = ['https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fen%2F8%2F84%2FAssociation_of_Gay_and_Lesbian_Psychiatrists_logo.jpg&f=1&nofb=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic01.nyt.com%2Fnewsgraphics%2F2016%2F07%2F14%2Fpluto-one-year%2Fassets%2Ficon-pluto.png&f=1&nofb=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse4.mm.bing.net%2Fth%3Fid%3DOIP.oFxADNN67dYP-ke5xg7HbQHaHG%26pid%3DApi&f=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.glassdoor.com%2Fsqll%2F1065746%2Felevation-church-squarelogo-1453223965790.png&f=1&nofb=1'];
for (const image of imagePath) {
// get the item id of an image
var slice = image.slice(26, 34);
var id = image;
var hdnName = document.getElementById("sendServ");
const img = document.createElement("img");
img.src = image;
img.classList.add("new");
img.id = slice;
const cnvs = document.createElement("canvas");
cnvs.classList.add("suiteiCanvas");
// cnvs.style.position = 'absolute';
cnvs.style.left = img.offsetLeft + "px";
cnvs.style.top = img.offsetTop + "px";
cnvs.style.display = 'none';
cnvs.width = 150;
cnvs.height = 150;
var ctx = cnvs.getContext("2d");
ctx.clearRect(0, 0, cnvs.width, cnvs.height);
ctx.beginPath();
ctx.arc(75, 75, 50, 0, 2 * Math.PI, false);
ctx.lineWidth = 15;
ctx.strokeStyle = '#FF0000';
ctx.stroke();
var div = document.createElement("div");
var div1 = document.createElement("div");
div.id = id;
div1.id = '1';
div.classList.add("image");
img.onclick = function draw() {
cnvs.style.display = '';
hdnName.value = img.id;
};
cnvs.onclick = function remove() {
cnvs.style.display = 'none';
hdnName.value = null;
};
document.getElementById('suitei-slider').appendChild(div);
document.getElementById(image).appendChild(img);
document.getElementById(image).appendChild(cnvs);
}
}
// slick slider
.image {
position: relative; /* add this */
user-select: none; /* and this maybe */
}
canvas.suiteiCanvas{
height: auto;
width: auto;
height: 150px;
max-width: 150px;
/*margin-left: 100px;
margin-right: 100px;*/
border:3px solid rgb(20, 11, 11);
position: absolute; /* add this */
}
#draw-btn {
font-size: 14px;
padding: 2px 16px 3px 16px;
margin-bottom: 8px;
}
img.new {
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
/*margin-left: 100px;
margin-right: 100px;*/
border:3px solid rgb(20, 11, 11);
}
<div class="multiple-items" id="suitei-slider"></div>
<input type="hidden" id="sendServ">
Just in case you wonder what each line means here it is a more clean code and with comments on many lines... for my understanding you code is a little confuse. You should use more times functions to be more readable, etc.
let index = 0;
const display = "table"; // or "grid" if horizontal, but this migh depend where you place the rest of the code, cause i added the style to the body
const x = 0;
const y = 0;
const images = {
height: 50,
width: 50,
url: [
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fen%2F8%2F84%2FAssociation_of_Gay_and_Lesbian_Psychiatrists_logo.jpg&f=1&nofb=1',
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic01.nyt.com%2Fnewsgraphics%2F2016%2F07%2F14%2Fpluto-one-year%2Fassets%2Ficon-pluto.png&f=1&nofb=1',
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse4.mm.bing.net%2Fth%3Fid%3DOIP.oFxADNN67dYP-ke5xg7HbQHaHG%26pid%3DApi&f=1',
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.glassdoor.com%2Fsqll%2F1065746%2Felevation-church-squarelogo-1453223965790.png&f=1&nofb=1'
]
}
function createHTML() {
console.log('E: Execute & R: Request & I: Informative');
//loop to go true all images
document.body.style.display = display;
for (const image of images.url) {
//each image will correspond to a canvas element
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
//each canvas element will has is own properties (in this case all the same)
canvas.id = 'option' + [index];
canvas.height = images.height;
canvas.width = images.width;
canvas.style.padding = '10px';
//function to get the corresponded image of that particular canvas element
drawImages(canvas);
//add an event listener for when a user click on the particular canvas
canvas.addEventListener("click", optionClick, false);
//all html part was handle we can append it to the body
document.body.appendChild(canvas);
index++;
}
}
function drawImages(canvas) {
//we need to use the getContext canvas function to draw anything inside the canvas element
const ctx = canvas.getContext('2d');
const background = new Image();
//This is needed because if the drawImage is called from a different place that the createHTML function
//index value will not be at 0 and it will for sure with an heigher id that the one expected
//so we are using regex to remove all letters from the canvas.id and get the number to use it later
index = canvas.id.replace(/\D/g, '');
//console.log('E: Drawing image ' + index + ' on canvas ' + canvas.id);
//get the image url using the index to get the corresponded image
background.src = images.url[index];
//no idea why but to place the image, we need to use the onload event
//https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
background.onload = function() {
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
}
}
function drawX(canvas) {
const ctx = canvas.getContext('2d');
console.log('E: Placing X on canvas ' + canvas.id);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(images.width, images.height);
ctx.moveTo(images.height, 0);
ctx.lineTo(0, images.width);
ctx.closePath();
ctx.stroke();
}
function clear(canvas) {
console.log('E: clearing canvas ' + canvas.id);
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
drawImages(canvas);
}
function optionClick(e) {
log = true;
const canvas = document.getElementsByTagName('canvas');
for (const option of canvas) {
if (log) console.log('I: User clicked at option ' + e.target.id + ':' + option.id);
log = false;
if (e.target.id === option.id) {
console.log('R: Drawing request at canvas ' + option.id);
drawX(option);
} else {
console.log('R: Clearing request at canvas ' + option.id);
clear(option);
}
}
}
//We start by calling createHTML (that will handle with all HTML elements)
window.onload = createHTML;
canvas.suiteiCanvas {
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
margin-left: 100px;
margin-right: 100px;
border: 3px solid rgb(20, 11, 11);
}
#draw-btn {
font-size: 14px;
padding: 2px 16px 3px 16px;
margin-bottom: 8px;
}
img.new {
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
margin-left: 100px;
margin-right: 100px;
border: 3px solid rgb(20, 11, 11);
}
<body></body>
By reference to Goomap map overlays DEMO https://google-developers.gonglchuangl.net/maps/documentation/javascript/examples/overlay-hideshow,I modify from this to dynamically load the map by create a element in a HTML File. However, I can see that Map Tiles shows correctly, but missing the overlays.
Besides, when I click the button, a TypeError occurs: overlay.toggle is not a function: TypeErrorPic
How can I dynamic load a Element in a HTML file?
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Showing/Hiding Overlays</title>
<style>
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
#floating-panel {
position: absolute;
top: 10px;
left: 25%;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
text-align: center;
font-family: 'Roboto','sans-serif';
line-height: 30px;
padding-left: 10px;
}
</style>
<!-- <script src="https://maps.googleapis.com/maps/api/js?key=MY_KEY"></script> -->
<script>
var myscript = document.createElement("script");
myscript.type = "text/javascript";
myscript.src = "https://maps.googleapis.com/maps/api/js?key=MY_KEY&callback=init_callback";
document.getElementsByTagName('head')[0].appendChild(myscript);
</script>
<script>
// This example adds hide() and show() methods to a custom overlay's prototype.
// These methods toggle the visibility of the container <div>.
// Additionally, we add a toggleDOM() method, which attaches or detaches the
// overlay to or from the map.
var overlay;
function init_callback()
{
USGSOverlay.prototype = new google.maps.OverlayView();
initMap();
}
function initMap() {
//alert("initMap");
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 11,
center: {lat: 62.323907, lng: -150.109291},
mapTypeId: 'satellite'
});
var bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(62.281819, -150.287132),
new google.maps.LatLng(62.400471, -150.005608));
// The photograph is courtesy of the U.S. Geological Survey.
var srcImage = 'https://developers.google.com/maps/documentation/javascript/';
srcImage += 'examples/full/images/talkeetna.png';
overlay = new USGSOverlay(bounds, srcImage, map);
}
/** #constructor */
function USGSOverlay(bounds, image, map) {
// Now initialize all properties.
this.bounds_ = bounds;
this.image_ = image;
this.map_ = map;
// Define a property to hold the image's div. We'll
// actually create this div upon receipt of the onAdd()
// method so we'll leave it null for now.
this.div_ = null;
// Explicitly call setMap on this overlay
this.setMap(map);
}
/**
* onAdd is called when the map's panes are ready and the overlay has been
* added to the map.
*/
USGSOverlay.prototype.onAdd = function() {
var div = document.createElement('div');
div.style.border = 'none';
div.style.borderWidth = '0px';
div.style.position = 'absolute';
// Create the img element and attach it to the div.
var img = document.createElement('img');
img.src = this.image_;
img.style.width = '100%';
img.style.height = '100%';
div.appendChild(img);
this.div_ = div;
// Add the element to the "overlayImage" pane.
var panes = this.getPanes();
panes.overlayImage.appendChild(this.div_);
};
USGSOverlay.prototype.draw = function() {
// We use the south-west and north-east
// coordinates of the overlay to peg it to the correct position and size.
// To do this, we need to retrieve the projection from the overlay.
var overlayProjection = this.getProjection();
// Retrieve the south-west and north-east coordinates of this overlay
// in LatLngs and convert them to pixel coordinates.
// We'll use these coordinates to resize the div.
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
// Resize the image's div to fit the indicated dimensions.
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = (ne.x - sw.x) + 'px';
div.style.height = (sw.y - ne.y) + 'px';
};
USGSOverlay.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
};
// Set the visibility to 'hidden' or 'visible'.
USGSOverlay.prototype.hide = function() {
if (this.div_) {
// The visibility property must be a string enclosed in quotes.
this.div_.style.visibility = 'hidden';
}
};
USGSOverlay.prototype.show = function() {
if (this.div_) {
this.div_.style.visibility = 'visible';
}
};
USGSOverlay.prototype.toggle = function() {
if (this.div_) {
if (this.div_.style.visibility === 'hidden') {
this.show();
} else {
this.hide();
}
}
};
// Detach the map from the DOM via toggleDOM().
// Note that if we later reattach the map, it will be visible again,
// because the containing <div> is recreated in the overlay's onAdd() method.
USGSOverlay.prototype.toggleDOM = function() {
if (this.getMap()) {
// Note: setMap(null) calls OverlayView.onRemove()
this.setMap(null);
} else {
this.setMap(this.map_);
}
};
//google.maps.event.addDomListener(window, 'load', initMap);
</script>
</head>
<body>
<!-- Add an input button to initiate the toggle method on the overlay. -->
<div id="floating-panel">
<input type="button" value="Toggle visibility" onclick="overlay.toggle();"></input>
<input type="button" value="Toggle DOM attachment" onclick="overlay.toggleDOM();"></input>
</div>
<div id="map"></div>
</body>
</html>
It is an ordering issue. You need to define the entire USGSOverlay class inside the init_callback function (that runs once the API is loaded). The way the code in your question runs, the init_callback function runs after the rest of the USGSOverlay class is defined and overwrites it.
proof of concept fiddle
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#floating-panel {
position: absolute;
top: 10px;
left: 25%;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
text-align: center;
font-family: 'Roboto', 'sans-serif';
line-height: 30px;
padding-left: 10px;
}
<script>
var myscript = document.createElement("script");
myscript.type = "text/javascript";
myscript.src = "https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=init_callback";
document.getElementsByTagName('head')[0].appendChild(myscript);
</script>
<script>
// This example adds hide() and show() methods to a custom overlay's prototype.
// These methods toggle the visibility of the container <div>.
// Additionally, we add a toggleDOM() method, which attaches or detaches the
// overlay to or from the map.
var overlay;
/** #constructor */
function USGSOverlay(bounds, image, map) {
// Now initialize all properties.
this.bounds_ = bounds;
this.image_ = image;
this.map_ = map;
// Define a property to hold the image's div. We'll
// actually create this div upon receipt of the onAdd()
// method so we'll leave it null for now.
this.div_ = null;
// Explicitly call setMap on this overlay
this.setMap(map);
}
function init_callback() {
USGSOverlay.prototype = new google.maps.OverlayView();
/**
* onAdd is called when the map's panes are ready and the overlay has been
* added to the map.
*/
USGSOverlay.prototype.onAdd = function() {
var div = document.createElement('div');
div.style.border = 'none';
div.style.borderWidth = '0px';
div.style.position = 'absolute';
// Create the img element and attach it to the div.
var img = document.createElement('img');
img.src = this.image_;
img.style.width = '100%';
img.style.height = '100%';
div.appendChild(img);
this.div_ = div;
// Add the element to the "overlayImage" pane.
var panes = this.getPanes();
panes.overlayImage.appendChild(this.div_);
};
USGSOverlay.prototype.draw = function() {
// We use the south-west and north-east
// coordinates of the overlay to peg it to the correct position and size.
// To do this, we need to retrieve the projection from the overlay.
var overlayProjection = this.getProjection();
// Retrieve the south-west and north-east coordinates of this overlay
// in LatLngs and convert them to pixel coordinates.
// We'll use these coordinates to resize the div.
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
// Resize the image's div to fit the indicated dimensions.
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = (ne.x - sw.x) + 'px';
div.style.height = (sw.y - ne.y) + 'px';
};
USGSOverlay.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
};
// Set the visibility to 'hidden' or 'visible'.
USGSOverlay.prototype.hide = function() {
if (this.div_) {
// The visibility property must be a string enclosed in quotes.
this.div_.style.visibility = 'hidden';
}
};
USGSOverlay.prototype.show = function() {
if (this.div_) {
this.div_.style.visibility = 'visible';
}
};
USGSOverlay.prototype.toggle = function() {
if (this.div_) {
if (this.div_.style.visibility === 'hidden') {
this.show();
} else {
this.hide();
}
}
};
// Detach the map from the DOM via toggleDOM().
// Note that if we later reattach the map, it will be visible again,
// because the containing <div> is recreated in the overlay's onAdd() method.
USGSOverlay.prototype.toggleDOM = function() {
if (this.getMap()) {
// Note: setMap(null) calls OverlayView.onRemove()
this.setMap(null);
} else {
this.setMap(this.map_);
}
};
initMap();
}
function initMap() {
//alert("initMap");
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 11,
center: {
lat: 62.323907,
lng: -150.109291
},
mapTypeId: 'satellite'
});
var bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(62.281819, -150.287132),
new google.maps.LatLng(62.400471, -150.005608));
// The photograph is courtesy of the U.S. Geological Survey.
var srcImage = 'https://developers.google.com/maps/documentation/javascript/';
srcImage += 'examples/full/images/talkeetna.png';
overlay = new USGSOverlay(bounds, srcImage, map);
}
//google.maps.event.addDomListener(window, 'load', initMap);
</script>
<!-- Add an input button to initiate the toggle method on the overlay. -->
<div id="floating-panel">
<input type="button" value="Toggle visibility" onclick="overlay.toggle();" />
<input type="button" value="Toggle DOM attachment" onclick="overlay.toggleDOM();" />
</div>
<div id="map"></div>
can you uncomment this line in your code and try again?
google.maps.event.addDomListener(window, 'load', initMap);
How can I customize the google maps api (v3 javascript) zoom buttons to my own image.
I am late at the party, but here is my two cents.
You have basically two options:
Option 1: You either create the controls using HTML/CSS yourself, which you can then place over the map to the correct position using position absolute or similar means. Even though this works in production, I don't like this, because your HTML/CSS for the element doesn't load at the same time than the map is displayed. Also you are separating your HTML/CSS code for the controls so it is harder to reuse the same map at different pages. e.g. "Did I forgot to add the controls?"
Option 2: You create a custom control which looks and feels the zoom controllers you like. Below is an code about this in practice.
In short, you need to first disable the normal UI controllers by calling:
var mapOptions = {
zoom: 12,
center: chicago,
/* Disabling default UI widgets */
disableDefaultUI: true // <-- see this line
}
And then you just create the controller and use it.
HTML:
...
<div id="map-canvas"></div>
...
CSS:
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px;
}
JavaScript:
var map;
var chicago = new google.maps.LatLng(41.850033, -87.6500523);
/**
* The ZoomControl adds +/- button for the map
*
*/
function ZoomControl(controlDiv, map) {
// Creating divs & styles for custom zoom control
controlDiv.style.padding = '5px';
// Set CSS for the control wrapper
var controlWrapper = document.createElement('div');
controlWrapper.style.backgroundColor = 'white';
controlWrapper.style.borderStyle = 'solid';
controlWrapper.style.borderColor = 'gray';
controlWrapper.style.borderWidth = '1px';
controlWrapper.style.cursor = 'pointer';
controlWrapper.style.textAlign = 'center';
controlWrapper.style.width = '32px';
controlWrapper.style.height = '64px';
controlDiv.appendChild(controlWrapper);
// Set CSS for the zoomIn
var zoomInButton = document.createElement('div');
zoomInButton.style.width = '32px';
zoomInButton.style.height = '32px';
/* Change this to be the .png image you want to use */
zoomInButton.style.backgroundImage = 'url("http://placehold.it/32/00ff00")';
controlWrapper.appendChild(zoomInButton);
// Set CSS for the zoomOut
var zoomOutButton = document.createElement('div');
zoomOutButton.style.width = '32px';
zoomOutButton.style.height = '32px';
/* Change this to be the .png image you want to use */
zoomOutButton.style.backgroundImage = 'url("http://placehold.it/32/0000ff")';
controlWrapper.appendChild(zoomOutButton);
// Setup the click event listener - zoomIn
google.maps.event.addDomListener(zoomInButton, 'click', function() {
map.setZoom(map.getZoom() + 1);
});
// Setup the click event listener - zoomOut
google.maps.event.addDomListener(zoomOutButton, 'click', function() {
map.setZoom(map.getZoom() - 1);
});
}
function initialize() {
var mapDiv = document.getElementById('map-canvas');
var mapOptions = {
zoom: 12,
center: chicago,
/* Disabling default UI widgets */
disableDefaultUI: true
}
map = new google.maps.Map(mapDiv, mapOptions);
// Create the DIV to hold the control and call the ZoomControl() constructor
// passing in this DIV.
var zoomControlDiv = document.createElement('div');
var zoomControl = new ZoomControl(zoomControlDiv, map);
zoomControlDiv.index = 1;
map.controls[google.maps.ControlPosition.TOP_LEFT].push(zoomControlDiv);
}
initialize();
Note: This code doesn't contain any fancy icons and a like, just placeholders. Therefore, you might need to tune it to fit your needs. Moreover, remember to add HTML5 normal tags and script include for the google maps api v3 javascript. I added only <div id="map-canvas"></div> because a need for rest of the body is pretty obvious.
To see it live: Here is working jsfiddle example
Cheers.
After hours searching I found the solution
Just make sure you replace your API ID!
Enjoy!
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map-canvas { height: 60%; width:60%; margin:20px auto; border:1px solid; padding-left:100px; }
</style>
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?key=YOUR-MAP_API-ID&sensor=false®ion=AU">
</script>
<script type="text/javascript">
function HomeControl(controlDiv, map) {
google.maps.event.addDomListener(zoomout, 'click', function() {
var currentZoomLevel = map.getZoom();
if(currentZoomLevel != 0){
map.setZoom(currentZoomLevel - 1);}
});
google.maps.event.addDomListener(zoomin, 'click', function() {
var currentZoomLevel = map.getZoom();
if(currentZoomLevel != 21){
map.setZoom(currentZoomLevel + 1);}
});
}
var map;
var markersArray = [];
function initialize() {
var mapDiv = document.getElementById('map-canvas');
var myLatlng = new google.maps.LatLng(-33.90224, 151.20215);
var mapOptions = {
zoom: 15,
center: myLatlng,
Marker: true,
panControl: false,
zoomControl: false,
streetViewControl: false,
overviewMapControl: false,
mapTypeControl: false,
mapTypeControl: false,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(mapDiv, mapOptions);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
title:"Hello World!"
});
// Create the DIV to hold the control and
// call the HomeControl() constructor passing
// in this DIV.
var homeControlDiv = document.createElement('div');
var homeControl = new HomeControl(homeControlDiv, map);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map-canvas"></div>
<div id="zoomout" style="border:1px solid; width:150px; cursor:pointer; margin-bottom:20px;">ZOOM ME OUT</div>
<div id="zoomin" style="border:1px solid; width:150px; cursor:pointer; ">ZOOM ME IN</div>
</body>
</html>
I did it in the CSS way:
#map-container .gm-style > .gmnoprint > .gmnoprint { background: url(/images/map-zoom-controls.png) no-repeat center center !important; width: 42px !important; height: 68px !important; }
#map-container .gm-style > .gmnoprint > .gmnoprint > div > img { display: none !important; }
#map-container .gm-style > .gmnoprint > .gmnoprint div[title="Zoom in"] { top: 2px !important; left: 2px !important; width: 38px !important; height: 31px !important; }
#map-container .gm-style > .gmnoprint > .gmnoprint div[title="Zoom out"] { top: 35px !important; left: 2px !important; width: 38px !important; height: 30px !important; }
This is for a image that has:
width: 42px;
height: 68px;
Make your own adjustments.
ATTENTION
This applies only if you are using English version because of the title attributes.
This isn't possible. You can tweak their appearance a bit using a set of predefined option parameters or you can implement your custom map control that provides the same functionality as the zoom control.
For more information see this page.
UPDATED: Link fixed. Thanks for the feedback!
I've been using code from http://gmaps-samples-v3.googlecode.com/svn/trunk/infowindow_custom/infowindow-custom.html, which is currently google's best example of how to create custom InfoWindow's in Maps API v3. I've been working on it and so far I've got it close to working except for one thing, it the div container the text content won't expand to fit the content, so it just drops off instead of expanding the bubble. if I give the content container a fixed pixel width it works fine but I can't get it to expand depending on the amount of text in it.
I've been stuck on this one for a while. Any help would be greatly appreciated!
Here is the HTML page
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Gayborhood Map Test</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px }
#map_canvas { width: 900px;
height: 400px;
margin: 200px auto 0 auto; }
</style>
<link rel="stylesheet" type="text/css" href="map.css" />
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script type="text/javascript" src="InfoBox.js"></script>
<script type="text/javascript">
function initialize() {
var latlng = new google.maps.LatLng(39.947137,-75.161824);
var myOptions = {
zoom: 16,
center: latlng,
mapTypeId: google.maps.MapTypeId.HYBRID
};
var gayborhood;
var map = new google.maps.Map(document.getElementById("map_canvas"),
myOptions);
var gayborhoodcoords = [
new google.maps.LatLng(39.9492017, -75.1631272),
new google.maps.LatLng(39.945423, -75.1639561),
new google.maps.LatLng(39.9450064, -75.160579),
new google.maps.LatLng(39.9487765, -75.1597468),
new google.maps.LatLng(39.9492017, -75.1631272)
];
gayborhood = new google.maps.Polygon({
paths: gayborhoodcoords,
strokeColor: "#00ff00",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#00ff00",
fillOpacity: 0.35
});
gayborhood.setMap(map);
var image = 'red_icon.png';
var myLatLng = new google.maps.LatLng(39.948883,-75.162246);
var redMarker = new google.maps.Marker({
position: myLatLng,
map: map,
icon: image
});
var contentString = '<h4>Woody\'s Bar</h4>';
/*var infowindow = new google.maps.InfoWindow({
content: contentString,
disableAutoPan: true
});*/
google.maps.event.addListener(redMarker, 'mouseover', function() {
var infoBox = new InfoBox({marker: redMarker, map: map});
});
/*google.maps.event.addListener(redMarker, 'mouseout', function() {
infowindow.close();
});*/
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
</html>
Here's the InfoBox.js:
/* An InfoBox is like an info window, but it displays
* under the marker, opens quicker, and has flexible styling.
* #param {GLatLng} latlng Point to place bar at
* #param {Map} map The map on which to display this InfoBox.
* #param {Object} opts Passes configuration options - content,
* offsetVertical, offsetHorizontal, className, height, width
*/
function InfoBox(opts) {
google.maps.OverlayView.call(this);
this.marker_ = opts.marker
this.latlng_ = opts.marker.getPosition();
this.map_ = opts.map;
this.offsetVertical_ = -65;
this.offsetHorizontal_ = -20;
this.height_ = 50;
//this.width_ = 159;
var me = this;
this.boundsChangedListener_ =
google.maps.event.addListener(this.map_, "bounds_changed", function() {
return me.panMap.apply(me);
});
// Once the properties of this OverlayView are initialized, set its map so
// that we can display it. This will trigger calls to panes_changed and
// draw.
this.setMap(this.map_);
}
/* InfoBox extends GOverlay class from the Google Maps API
*/
InfoBox.prototype = new google.maps.OverlayView();
/* Creates the DIV representing this InfoBox
*/
InfoBox.prototype.remove = function() {
if (this.div_) {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
};
/* Redraw the Bar based on the current projection and zoom level
*/
InfoBox.prototype.draw = function() {
// Creates the element if it doesn't exist already.
this.createElement();
if (!this.div_) return;
// Calculate the DIV coordinates of two opposite corners of our bounds to
// get the size and position of our Bar
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
if (!pixPosition) return;
// Now position our DIV based on the DIV coordinates of our bounds
//this.div_.style.width = this.width_ + "px";
this.div_.style.left = (pixPosition.x + this.offsetHorizontal_) + "px";
this.div_.style.height = this.height_ + "px";
this.div_.style.top = (pixPosition.y + this.offsetVertical_) + "px";
this.div_.style.display = 'block';
};
/* Creates the DIV representing this InfoBox in the floatPane. If the panes
* object, retrieved by calling getPanes, is null, remove the element from the
* DOM. If the div exists, but its parent is not the floatPane, move the div
* to the new pane.
* Called from within draw. Alternatively, this can be called specifically on
* a panes_changed event.
*/
InfoBox.prototype.createElement = function() {
var panes = this.getPanes();
var div = this.div_;
if (!div) {
// This does not handle changing panes. You can set the map to be null and
// then reset the map to move the div.
div = this.div_ = document.createElement("div");
div.className = "infobox";
//div.style.width = this.width_ + "px";
//div.style.height = this.height_ + "px";
var leftDiv = document.createElement("div");
leftDiv.className = "bubbleLeftDiv";
var containerDiv = document.createElement("div");
containerDiv.className = "infoboxContainer";
var contentDiv = document.createElement("div");
contentDiv.className = "infoboxContent";
var title = "Much longer title than woody's"
//var infoboxWidth = ( title.length*10 - (title.length) - 40) + "px"
//containerDiv.style.width = infoboxWidth;
//this.width_ = infoboxWidth + 47;
contentDiv.innerHTML = "<h3>" + title + "</h3>";
var rightDiv = document.createElement("div");
rightDiv.className = "bubbleRightDiv";
function removeInfoBox(ib) {
return function() {
ib.setMap(null);
};
}
google.maps.event.addListener(this.marker_, 'mouseout', removeInfoBox(this));
div.appendChild(leftDiv)
div.appendChild(containerDiv);
containerDiv.appendChild(contentDiv);
div.appendChild(rightDiv);
div.style.display = 'none';
panes.floatPane.appendChild(div);
this.panMap();
} else if (div.parentNode != panes.floatPane) {
// The panes have changed. Move the div.
div.parentNode.removeChild(div);
panes.floatPane.appendChild(div);
} else {
// The panes have not changed, so no need to create or move the div.
}
}
/* Pan the map to fit the InfoBox.
*/
InfoBox.prototype.panMap = function() {
// if we go beyond map, pan map
var map = this.map_;
var bounds = map.getBounds();
if (!bounds) return;
// The position of the infowindow
var position = this.latlng_;
// The dimension of the infowindow
var iwWidth = this.width_;
var iwHeight = this.height_;
// The offset position of the infowindow
var iwOffsetX = this.offsetHorizontal_;
var iwOffsetY = this.offsetVertical_;
// Padding on the infowindow
var padX = 40;
var padY = 40;
// The degrees per pixel
var mapDiv = map.getDiv();
var mapWidth = mapDiv.offsetWidth;
var mapHeight = mapDiv.offsetHeight;
var boundsSpan = bounds.toSpan();
var longSpan = boundsSpan.lng();
var latSpan = boundsSpan.lat();
var degPixelX = longSpan / mapWidth;
var degPixelY = latSpan / mapHeight;
// The bounds of the map
var mapWestLng = bounds.getSouthWest().lng();
var mapEastLng = bounds.getNorthEast().lng();
var mapNorthLat = bounds.getNorthEast().lat();
var mapSouthLat = bounds.getSouthWest().lat();
// The bounds of the infowindow
var iwWestLng = position.lng() + (iwOffsetX - padX) * degPixelX;
var iwEastLng = position.lng() + (iwOffsetX + iwWidth + padX) * degPixelX;
var iwNorthLat = position.lat() - (iwOffsetY - padY) * degPixelY;
var iwSouthLat = position.lat() - (iwOffsetY + iwHeight + padY) * degPixelY;
// calculate center shift
var shiftLng =
(iwWestLng < mapWestLng ? mapWestLng - iwWestLng : 0) +
(iwEastLng > mapEastLng ? mapEastLng - iwEastLng : 0);
var shiftLat =
(iwNorthLat > mapNorthLat ? mapNorthLat - iwNorthLat : 0) +
(iwSouthLat < mapSouthLat ? mapSouthLat - iwSouthLat : 0);
// The center of the map
var center = map.getCenter();
// The new map center
var centerX = center.lng() - shiftLng;
var centerY = center.lat() - shiftLat;
// center the map to the new shifted center
map.setCenter(new google.maps.LatLng(centerY, centerX));
// Remove the listener after panning is complete.
google.maps.event.removeListener(this.boundsChangedListener_);
this.boundsChangedListener_ = null;
};
And here's the CSS:
.infobox {
border: 0px none;
position: absolute;
width: auto;
height: auto;
}
.infoboxContent {
font-family: arial, helvetica, sans-serif;
font-size: 15px;
padding: 0px;
margin: 9px 0px 0px -24px;
position: absolute;
z-index: 105;
}
.infoboxContainer {
background: url('infowindow_bg.png') repeat-x;
height: 50px;
margin-left: 47px;
}
.bubbleLeftDiv {
width: 47px;
height: 50px;
background: url('infowindow_left.png') no-repeat;
position: absolute;
z-index: 102;
}
.bubbleRightDiv {
width: 26px;
height: 50px;
background: url('infowindow_right.png') no-repeat;
position: absolute;
right: -26px;
top: 0px;
}
.clear { clear: both; }
Thank you!!
I've faced the same problem. The approach that worked for me was to dynamically determine the dimensions of the content and set the height and width of the InfoBox correctly. The problem that I encountered with this was that before the content is inserted into the DOM it doesn't have (correct) dimension values. As a result my approach was the following:
Create the DOM content element that needs to be inserted
Insert it in a temp container
Get dimensions of temp container
Remove temp container
Insert content in InfoBox and set its height and width based on temp container dimensions
Here is an example done with the jQuery framework:
var temp = $("<div class='temp'></div>").html(content).hide().appendTo("body");
var dimentions = {
width : temp.outerWidth(true),
height : temp.outerHeight(true)
};
temp.remove();
var overlayProjection = this.getProjection();
var top_left = overlayProjection.fromLatLngToDivPixel(this.point_);
var dimensions= $.extend({}, dimensions, {
y : top_left.y - dimensions.height,
x : top_left.x - dimensions.width/2
});
var div = this.div_;
$(div).css({
"top": dimensions.y + 'px',
"left" : dimensions.x + 'px',
"width" : dimensions.width + 'px',
"height" : dimensions.height + 'px'
}).html(content);
Hope that helps!
You may override the the infobox draw method and reposition the infobox after it has been rendered:
var infobox = new InfoBox(myOptions);
infobox.initReady = false;
var oldDraw = infobox.draw;
infobox.draw = function() {
oldDraw.apply(this);
if( ! infobox.initReady) {
// Calculate the required offset
var offsetY = -($(infobox.div_).outerHeight());
var offsetX = -110;
infobox.initReady = true;
// Set the new pixelOffset
infobox.setOptions({
pixelOffset: new google.maps.Size(offsetX, offsetY)
});
}
}