So for my assignment I have a webpage where I input a number and choose a shape and the chosen number amount of the chosen shape will appear and go through a set animation. After the animation, the shape will disappear, but before that, the shape should also disappear if its clicked. I've tried to use the remove() function but can't get this right. Please help.
Here's my javascript:
draw = function() {
var typed = $('#howmany').val()
var shape = $('#shape').val()
var SVG = $("svg");
var x, y;
for (var i = 0; i < typed; i++) {
x = Math.random() * 350
y = Math.random() * 350
if (shape == 'a') {
pattern = paper.circle(25, 25, 25)
}
if (shape == 'b') {
pattern = paper.rect(10, 10, 50, 50)
}
if (shape == 'c') {
pattern = paper.path('M25,0 L50,50, L0,50 Z')
}
color_attr = {
'fill': '#BB7'
}
position_attr = {
'transform': 't' + x + ',' + y
}
pattern.attr(color_attr)
pattern.animate(position_attr, 2000)
pattern.click(remove())
setTimeout(function(){
SVG.find("circle").remove();
SVG.find("rect").remove();
SVG.find("path").remove();
}, 2000);
}
}
setup = function() {
paper = Raphael('svg1', 400, 400)
$('button').click(draw)
}
jQuery(document).ready(setup)
Here's the fiddle: https://jsfiddle.net/o6e2yu5b/3/
You need to bind the click event to the pattern and remove it, when clicked. As per your code, you need to attach an event handler using an IIFE, so you dont run into issues with the closure.
Here is what you could do.
(function(currentPattern) {
currentPattern.node.onclick = function(){
currentPattern.remove();
};
})(pattern);
Here is the updated jsFiddle
i don't know if you need to remove all the shapes or one specific clicked shape. In case you want to remove all, just add this function before your setTimeout function:
$('body').click(function(e){
var element = e.target.tagName;
if (element === 'circle')
{ SVG.find(element).remove(); }
});
if you want to delete a specific one, try to identify each circle by an ID then make an if statement to delete the shape that has the ID of the clicked element.
I guess you needed a small rectification in your JS snippet and it work. Just replace the removal code with this:
pattern.click(pattern.remove)
draw = function() {
var typed = $('#howmany').val()
var shape = $('#shape').val()
var SVG = $("svg");
var x, y;
for (var i = 0; i < typed; i++) {
x = Math.random() * 350
y = Math.random() * 350
if (shape == 'a') {
pattern = paper.circle(25, 25, 25)
}
if (shape == 'b') {
pattern = paper.rect(10, 10, 50, 50)
}
if (shape == 'c') {
pattern = paper.path('M25,0 L50,50, L0,50 Z')
}
color_attr = {
'fill': '#BB7'
}
position_attr = {
'transform': 't' + x + ',' + y
}
pattern.attr(color_attr)
pattern.animate(position_attr, 2000)
pattern.click(pattern.remove)
setTimeout(function(){
SVG.find("circle").remove();
SVG.find("rect").remove();
SVG.find("path").remove();
}, 2000);
}
}
setup = function() {
paper = Raphael('svg1', 400, 400)
$('button').click(draw)
}
jQuery(document).ready(setup)
body {
max-width: 40em;
line-height: 1.6;
margin: 0 auto;
padding: 0.5em;
color: black;
font-family: "Helvetica", "Arial", sans-serif;
}
h1,
h2,
h3 {
line-height: 1.2;
color: black;
}
#media print {
body {
line-height: 1.4;
}
}
svg {
border: thin solid black;
}
input {
width: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<main>
<h1>Assignment 4 : Zap-em</h1>
<p>Difficulty:
<input type="text" id="howmany" />
</p>
<p>
Shape:
<select id="shape">
<option value="a">Circle</option>
<option value="b">Square</option>
<option value="c">Triangle</option>
</select>
</p>
<button id="btn">Start</button>
<div id="svg1"></div>
</main>
Updated fiddle here.
Read more about click and remove.
Related
I have this little block that I move around using javascript code. It works all good except if I keep moving it, it can easily get out of the box where it is supposed to be.
Can I prevent this somehow? So no matter how far I want to move it, it will stay stuck inside of the container/box ?
Here's my snippet code:
/// store key codes and currently pressed ones
var keys = {};
keys.UP = 38;
keys.LEFT = 37;
keys.RIGHT = 39;
keys.DOWN = 40;
/// store reference to character's position and element
var character = {
x: 100,
y: 100,
speedMultiplier: 2,
element: document.getElementById("character")
};
var is_colliding = function(div1, div2) {
var d1_height = div1.offsetHeight;
var d1_width = div1.offsetWidth;
var d1_distance_from_top = div1.offsetTop + d1_height;
var d1_distance_from_left = div1.offsetLeft + d1_width;
var d2_height = div2.offsetHeight;
var d2_width = div2.offsetWidth;
var d2_distance_from_top = div2.offsetTop + d2_height;
var d2_distance_from_left = div2.offsetLeft + d2_width;
var not_colliding =
d1_distance_from_top <= div2.offsetTop ||
div1.offsetTop >= d2_distance_from_top ||
d1_distance_from_left <= div2.offsetTop ||
div1.offsetLeft >= d2_distance_from_left;
return !not_colliding;
};
/// key detection (better to use addEventListener, but this will do)
document.body.onkeyup =
document.body.onkeydown = function(e){
if (e.preventDefault) {
e.preventDefault();
}
else {
e.returnValue = false;
}
var kc = e.keyCode || e.which;
keys[kc] = e.type == 'keydown';
};
/// character movement update
var moveCharacter = function(dx, dy){
character.x += (dx||0) * character.speedMultiplier;
character.y += (dy||0) * character.speedMultiplier;
character.element.style.left = character.x + 'px';
character.element.style.top = character.y + 'px';
};
/// character control
var detectCharacterMovement = function(){
if ( keys[keys.LEFT] ) {
moveCharacter(-5, 0);
}
if ( keys[keys.RIGHT] ) {
moveCharacter(5, 0);
}
if ( keys[keys.UP] ) {
moveCharacter(0, -5);
}
if ( keys[keys.DOWN] ) {
moveCharacter(0, 5);
}
};
/// update current position on screen
moveCharacter();
/// game loop
setInterval(function(){
detectCharacterMovement();
}, 1000/24);
body{
display: flex;
justify-content: center;
align-items: center;
}
#character {
position: absolute;
width: 42px;
height: 42px;
background: red;
z-index:99;
}
#container{
width: 400px;
height: 400px;
background: transparent;
border:5px solid rgb(0, 0, 0);
position: relative;
overflow: hidden;
}
<div id="container">
<div id="character"></div>
</div>
PS: You can move the box using keyboard arrows.
Get the container width and height into variable and set a condition on your move
var moveCharacter = function(dx, dy){
let div_width = document.getElementById('container').clientWidth;
let div_height = document.getElementById('container').clientHeight;
if((div_width - character.x) < 50 ){ // 50 = width of character and padding
character.x = div_width - 50;
}
if(character.x < 10){ // Padding
character.x = 11;
}
if((div_height - character.y) < 50 ){
character.y = div_height - 50;
}
if(character.y < 10){
character.y = 11;
}
I am trying to add an onclick listener to my classes. I have added alert() methods inside but it seems that the onclick is never executed. I have tried to use event listeners too which gave me the same issue.
My code is below, how can I loop through my classes and append an event listener?
/* Easier function to use than rewriting document.get... */
function getById(id) {
return document.getElementById(id)
}
function getByClass(c) {
return document.getElementsByClassName(c)
}
/* Random number calculation */
function random(xOrY) {
return Math.floor(Math.random() * (+xOrY - +1)) + 1
}
/* Create a grid */
function createGrid(isHiding) {
var grid = document.createElement("div")
grid.className = isHiding ? "hiding" : "grid"
return grid
}
/* Set configurations we will use */
var settings = {
hiding: 4,
x: 6,
y: 6,
maxAttempts: 6 * 6,
container: 'grid-container'
}
/* Set up the game */
var game = {
settings: settings,
attempts: 0,
numberFound: 0,
hidingGrids: []
}
/* Generate the hiding grids */
for (i = 1; i <= game.settings.hiding; i++) {
game.hidingGrids.push({
x: random(game.settings.x),
y: random(game.settings.y)
})
}
/* Generate the grids */
for (y = 1; y <= game.settings.y; y++) {
for (x = 1; x <= game.settings.x; x++) {
var gridHasHid = false
game.hidingGrids.forEach(function(grid) {
if (y == grid.y && x == grid.x) {
gridHasHid = true
/* Create a hidden grid */
getById(game.settings.container).appendChild(createGrid(true))
}
})
if (!gridHasHid) {
/**
*If it gets here, the grid wasn't a hidden grid
* thus we need to add a standard grid.
*/
getById(game.settings.container).appendChild(createGrid(false))
}
}
/* Append a break line to start the next row */
var br = document.createElement("br")
getById(game.settings.container).appendChild(br)
}
/* Lets set listen handlers on the incorrect and correct grids */
for (el in getByClass("grid")) {
el.onclick = function() {
/* We need to go through all our checks to ensure the game hasn't ended */
if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) {
alert("The game is already over.")
return
}
/* Check that the tile hasn't already been clicked using our colour factor */
if (this.style.background == "red") return
/* If it got here, all checks passed. Lets update the colour and send an message */
this.style.background = "red"
alert("Incorrect, you have " + ++game.attempts + " attempts left.")
}
}
for (el in getByClass("hiding")) {
el.onclick = function() {
/* We need to go through all our checks to ensure the game hasn't ended */
if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) {
alert("The game is already over.")
return
}
/* Check that the tile hasn't already been clicked using our colour factor */
if (this.style.background == "blue") return
/* If it got here, all checks passed. Lets update the colour and send an message */
this.style.background = "blue"
alert("Correct, you have " + ++game.attempts + " attempts left.")
}
}
#grid-container {
display: inline-block;
width: 100%;
}
.grid {
display: inline-block;
background-color: #000;
padding: 5%;
margin: 2%;
}
.hiding {
background-color: #000;
display: inline-block;
padding: 5%;
margin: 2%;
}
/* Uncomment below to see where they're hiding (DEBUG) */
.hiding {
background-color: blue;
}
<div id="grid-container"></div>
you have some mistakes in your code. for eg. in this for loop for (el in getByClass("hiding")) the el will only give you the key value not the entire element.
you need to get the element like this getByClass("hiding")[el].onclick = function() {
I have added some code. Try this
/* Easier function to use than rewriting document.get... */
function getById(id) {
return document.getElementById(id)
}
function getByClass(c) {
return document.getElementsByClassName(c)
}
/* Random number calculation */
function random(xOrY) {
return Math.floor(Math.random() * (+xOrY - +1)) + 1
}
/* Create a grid */
function createGrid(isHiding) {
var grid = document.createElement("div")
grid.className = isHiding ? "hiding" : "grid"
return grid
}
/* Set configurations we will use */
var settings = {
hiding: 4,
x: 6,
y: 6,
maxAttempts: 6 * 6,
container: 'grid-container'
}
/* Set up the game */
var game = {
settings: settings,
attempts: 0,
numberFound: 0,
hidingGrids: []
}
/* Generate the hiding grids */
for (i = 1; i <= game.settings.hiding; i++) {
game.hidingGrids.push({
x: random(game.settings.x),
y: random(game.settings.y)
})
}
/* Generate the grids */
for (y = 1; y <= game.settings.y; y++) {
for (x = 1; x <= game.settings.x; x++) {
var gridHasHid = false
game.hidingGrids.forEach(function(grid) {
if (y == grid.y && x == grid.x) {
gridHasHid = true
/* Create a hidden grid */
getById(game.settings.container).appendChild(createGrid(true))
}
})
if (!gridHasHid) {
/**
*If it gets here, the grid wasn't a hidden grid
* thus we need to add a standard grid.
*/
getById(game.settings.container).appendChild(createGrid(false))
}
}
/* Append a break line to start the next row */
var br = document.createElement("br")
getById(game.settings.container).appendChild(br)
}
/* Lets set listen handlers on the incorrect and correct grids */
for (el in getByClass("grid")) {
getByClass("grid")[el].onclick = function() {
/* We need to go through all our checks to ensure the game hasn't ended */
if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) {
alert("The game is already over.")
return
}
/* Check that the tile hasn't already been clicked using our colour factor */
if (this.style.background == "red") return
/* If it got here, all checks passed. Lets update the colour and send an message */
this.style.background = "red"
alert("Incorrect, you have " + ++game.attempts + " attempts left.")
}
}
for (el in getByClass("hiding")) {
getByClass("hiding")[el].onclick = function() {
/* We need to go through all our checks to ensure the game hasn't ended */
if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) {
alert("The game is already over.")
return
}
/* Check that the tile hasn't already been clicked using our colour factor */
if (this.style.background == "blue") return
/* If it got here, all checks passed. Lets update the colour and send an message */
this.style.background = "blue"
alert("Correct, you have " + ++game.attempts + " attempts left.")
}
}
#grid-container {
display: inline-block;
width: 100%;
}
.grid {
display: inline-block;
background-color: #000;
padding: 5%;
margin: 2%;
}
.hiding {
background-color: #000;
display: inline-block;
padding: 5%;
margin: 2%;
}
/* Uncomment below to see where they're hiding (DEBUG) */
.hiding {
background-color: blue;
}
<div id="grid-container"></div>
I know this isn't CodeReview but I tried to improve your code and fix the issue.
Array.from(getByClass("grid")).forEach(el => {
el.addEventListener('click', () => {
// your code
});
});
This converts the array-like object of all child elements returned by Document.getElementsByClassName() into an actual array and iterates through each element. You also have to use to el instead of this here because there is no this defined in that scope.
function getById(id) {
return document.getElementById(id)
}
function getByClass(c) {
return document.getElementsByClassName(c)
}
/* Random number calculation */
function random(xOrY) {
return Math.floor(Math.random() * (+xOrY - +1)) + 1
}
/* Create a grid */
function createGrid(isHiding) {
let grid = document.createElement("div");
grid.className = isHiding ? "hiding" : "grid";
return grid
}
/* Set configurations we will use */
let settings = {
hiding: 4,
x: 6,
y: 6,
maxAttempts: 6 * 6,
container: 'grid-container'
};
/* Set up the game */
let game = {
settings: settings,
attempts: 0,
numberFound: 0,
hidingGrids: []
};
/* Generate the hiding grids */
for (let i = 1; i <= game.settings.hiding; i++) {
game.hidingGrids.push({
x: random(game.settings.x),
y: random(game.settings.y)
})
}
/* Generate the grids */
for (let y = 1; y <= game.settings.y; y++) {
for (let x = 1; x <= game.settings.x; x++) {
let gridHasHid = false;
game.hidingGrids.forEach(function(grid) {
if (y === grid.y && x === grid.x) {
gridHasHid = true;
/* Create a hidden grid */
getById(game.settings.container).appendChild(createGrid(true))
}
});
if (!gridHasHid) {
/**
*If it gets here, the grid wasn't a hidden grid
* thus we need to add a standard grid.
*/
getById(game.settings.container).appendChild(createGrid(false))
}
}
/* Append a break line to start the next row */
let br = document.createElement("br")
getById(game.settings.container).appendChild(br)
}
/* Lets set listen handlers on the incorrect and correct grids */
Array.from(getByClass("grid")).forEach(el => {
el.addEventListener('click', () => {
/* We need to go through all our checks to ensure the game hasn't ended */
if (game.attempts === game.settings.maxAttempts || game.numberFound === game.settings.hiding) {
alert("The game is already over.");
return
}
/* Check that the tile hasn't already been clicked using our colour factor */
if (el.style.background === "red") return;
/* If it got here, all checks passed. Lets update the colour and send an message */
el.style.background = "red";
alert(`Incorrect, you have ${++game.attempts} attempts left.`)
});
});
Array.from(getByClass("hiding")).forEach(el => {
el.addEventListener('click', () => {
/* We need to go through all our checks to ensure the game hasn't ended */
if (game.attempts === game.settings.maxAttempts || game.numberFound === game.settings.hiding) {
alert("The game is already over.");
return
}
/* Check that the tile hasn't already been clicked using our colour factor */
if (el.style.background === "blue") return;
/* If it got here, all checks passed. Lets update the colour and send an message */
el.style.background = "blue";
alert(`Correct, you have ${++game.attempts} attempts left.`)
});
});
#grid-container {
display: inline-block;
width: 100%;
}
.grid {
display: inline-block;
background-color: #000;
padding: 5%;
margin: 2%;
}
.hiding {
background-color: #000;
display: inline-block;
padding: 5%;
margin: 2%;
}
/* Uncomment below to see where they're hiding (DEBUG) */
.hiding {
background-color: blue;
}
<div id="grid-container"></div>
getElementsByClassName returns NodeList array. and for...in is used to iterate over the properties of objects. so use basic for loop to iterate over array here.
Try below solution.
/* Easier function to use than rewriting document.get... */
function getById(id) {
return document.getElementById(id)
}
function getByClass(c) {
return document.getElementsByClassName(c)
}
/* Random number calculation */
function random(xOrY) {
return Math.floor(Math.random() * (+xOrY - +1)) + 1
}
/* Create a grid */
function createGrid(isHiding) {
var grid = document.createElement("div")
grid.className = isHiding ? "hiding" : "grid"
return grid
}
/* Set configurations we will use */
var settings = {
hiding: 4,
x: 6,
y: 6,
maxAttempts: 6 * 6,
container: 'grid-container'
}
/* Set up the game */
var game = {
settings: settings,
attempts: 0,
numberFound: 0,
hidingGrids: []
}
/* Generate the hiding grids */
for (i = 1; i <= game.settings.hiding; i++) {
game.hidingGrids.push({
x: random(game.settings.x),
y: random(game.settings.y)
})
}
/* Generate the grids */
for (y = 1; y <= game.settings.y; y++) {
for (x = 1; x <= game.settings.x; x++) {
var gridHasHid = false
game.hidingGrids.forEach(function(grid) {
if (y == grid.y && x == grid.x) {
gridHasHid = true
/* Create a hidden grid */
getById(game.settings.container).appendChild(createGrid(true))
}
})
if (!gridHasHid) {
/**
*If it gets here, the grid wasn't a hidden grid
* thus we need to add a standard grid.
*/
getById(game.settings.container).appendChild(createGrid(false))
}
}
/* Append a break line to start the next row */
var br = document.createElement("br")
getById(game.settings.container).appendChild(br)
}
/* Lets set listen handlers on the incorrect and correct grids */
for (el in getByClass("grid")) {
el.onclick = function() {
/* We need to go through all our checks to ensure the game hasn't ended */
if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) {
alert("The game is already over.")
return
}
/* Check that the tile hasn't already been clicked using our colour factor */
if (this.style.background == "red") return
/* If it got here, all checks passed. Lets update the colour and send an message */
this.style.background = "red"
alert("Incorrect, you have " + ++game.attempts + " attempts left.")
}
}
var hidingClass = getByClass("hiding");
for(var i = 0; i < hidingClass.length; i++){
var el = hidingClass[i];
el.onclick = function() {
/* We need to go through all our checks to ensure the game hasn't ended */
if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) {
alert("The game is already over.")
return
}
/* Check that the tile hasn't already been clicked using our colour factor */
if (this.style.background == "blue") return
/* If it got here, all checks passed. Lets update the colour and send an message */
this.style.background = "blue"
alert("Correct, you have " + ++game.attempts + " attempts left.")
}
}
#grid-container {
display: inline-block;
width: 100%;
}
.grid {
display: inline-block;
background-color: #000;
padding: 5%;
margin: 2%;
}
.hiding {
background-color: #000;
display: inline-block;
padding: 5%;
margin: 2%;
}
/* Uncomment below to see where they're hiding (DEBUG) */
.hiding {
background-color: blue;
}
<div id="grid-container"></div>
I'm doing this plugin that takes the words and makes them pulsate on the screen:
First they appear and grow, then they vanish, change place and again appear
Working plugin:
+ function($) {
var Pulsate = function(element) {
var self = this;
self.element = element;
self.max = 70;
self.min = 0;
self.speed = 500;
self.first = true;
self.currentPlace;
self.possiblePlaces = [
{
id: 0,
top: 150,
left: 150,
},
{
id: 1,
top: 250,
left: 250,
},
{
id: 2,
top: 350,
left: 350,
},
{
id: 3,
top: 250,
left: 750,
},
{
id: 4,
top: 450,
left: 950,
}
];
};
Pulsate.prototype.defineRandomPlace = function() {
var self = this;
self.currentPlace = self.possiblePlaces[Math.floor(Math.random() * self.possiblePlaces.length)];
if(!self.possiblePlaces) self.defineRandomPlace;
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
};
Pulsate.prototype.animateToZero = function() {
var self = this;
self.element.animate({
'fontSize': 0,
'queue': true
}, self.speed, function() {
self.defineRandomPlace();
});
};
Pulsate.prototype.animateToRandomNumber = function() {
var self = this;
self.element.animate({
'fontSize': Math.floor(Math.random() * (70 - 50 + 1) + 50),
'queue': true
}, self.speed, function() {
self.first = false;
self.start();
});
};
Pulsate.prototype.start = function() {
var self = this;
if (self.first) self.defineRandomPlace();
if (!self.first) self.animateToZero();
self.animateToRandomNumber();
};
$(window).on('load', function() {
$('[data-pulsate]').each(function() {
var element = $(this).data('pulsate') || false;
if (element) {
element = new Pulsate($(this));
element.start();
}
});
});
}(jQuery);
body {
background: black;
color: white;
}
.word {
position: absolute;
text-shadow: 0 0 5px rgba(255, 255, 255, 0.9);
font-size: 0px;
}
.two {
position: absolute;
color: white;
left: 50px;
top: 50px;
}
div {
margin-left: 0px;
}
<span class="word" data-pulsate="true">Love</span>
<span class="word" data-pulsate="true">Enjoy</span>
<span class="word" data-pulsate="true">Huggs</span>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
If you notice i define the places that the word can grow in the self.possiblePlaces, and if you notice the animation, sometimes more then one word can grow in one place, my goal coming here is ask for help. How I can make two words never grow in the same place??
I was trying to do like this:
In the defineRandomPlace i pick a random object inside my possiblePlaces array:
Pulsate.prototype.defineRandomPlace = function() {
var self = this;
self.currentPlace = self.possiblePlaces[Math.floor(Math.random() * self.possiblePlaces.length)];
if(!self.possiblePlaces) self.defineRandomPlace;
delete self.possiblePlaces[self.currentPlace.id];
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
};
Notice the delete, first i clone the chosen object, after I delete it but keep his place in the array.
After the animation was over, I put the object in the array again, before starting all over again:
Pulsate.prototype.animateToZero = function() {
var self = this;
self.element.animate({
'fontSize': 0,
'queue': true
}, self.speed, function() {
self.possiblePlaces[self.currentPlace.id] = self.currentPlace;
self.defineRandomPlace();
});
But it made no difference.
Thanks!!
Pen: http://codepen.io/anon/pen/waooQB
In your example, you are randomly picking from a list that has five members, and you have three separate words that could be displayed, putting chance of overlap fairly high.
A simple approach to resolve is to pick the first item in the list, remove it from the list, and the append to the end of the list each time. Because you have more positions in the lists than items selecting from it, you're guaranteed to never collide.
Share the same list possiblePlaces between all instances.
Shift the first item off the queue, and push it onto the end when its done each time, instead of selecting randomly in defineRandomPlace.
Snippet highlighting #2:
// shift a position off the front
self.currentPlace = possiblePlaces.shift();
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
// push it back on the end
possiblePlaces.push(self.currentPlace);
If you want it truly random, you'll need to randomly select and remove an item from the array, and not put it back into the array until after it's been used. You'll also need to always ensure that you have more possiblePlaces than you have dom elements to place on the page.
Like so:
Pulsate.prototype.defineRandomPlace = function() {
var self = this;
var newPlace = possiblePlaces.splice(Math.floor(Math.random()*possiblePlaces.length), 1)[0];
if (self.currentPlace) {
possiblePlaces.push(self.currentPlace);
}
self.currentPlace = newPlace;
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
};
See http://codepen.io/anon/pen/bdBBPE
My decision is to divide the page to imaginary rows and to prohibit more than one word in the same row. Please check this out.
Note: as the code currently does not support recalculating of rows count on document resize, the full page view will not display correctly. Click "reload frame" or try JSFiddle or smth.
var pulsar = {
// Delay between words appearance
delay: 400,
// Word animation do not really depend on pulsar.delay,
// but if you set pulsar.delay small and wordAnimationDuration long
// some words will skip their turns. Try 1, 2, 3...
wordAnimationDuration: 400 * 3,
// Depending on maximum font size of words we calculate the number of rows
// to which the window can be divided
maxFontSize: 40,
start: function () {
this.computeRows();
this.fillWords();
this.animate();
},
// Calculate the height or row and store each row's properties in pulsar.rows
computeRows: function () {
var height = document.body.parentNode.clientHeight;
var rowsCount = Math.floor(height/this.maxFontSize);
this.rows = [];
for (var i = 0; i < rowsCount; i++) {
this.rows.push({
index: i,
isBusy: false
});
}
},
// Store Word instances in pulsar.words
fillWords: function () {
this.words = [];
var words = document.querySelectorAll('[data-pulsate="true"]');
for (var i = 0; i < words.length; i++) {
this.words.push(new Word(words[i], this.wordAnimationDuration, this.maxFontSize));
}
},
// When it comes time to animate another word we need to know which row to move it in
// this random row should be empty at the moment
getAnyEmptyRowIndex: function () {
var emptyRows = this.rows.filter(function(row) {
return !row.isBusy;
});
if (emptyRows.length == 0) {
return -1;
}
var index = emptyRows[Math.floor(Math.random() * emptyRows.length)].index;
this.rows[index].isBusy = true;
return index;
},
// Here we manipulate words in order of pulsar.words array
animate: function () {
var self = this;
this.interval = setInterval(function() {
var ri = self.getAnyEmptyRowIndex();
if (ri >= 0) {
self.words.push(self.words.shift());
self.words[0].animate(ri, function () {
self.rows[ri].isBusy = false;
});
}
}, this.delay);
}
}
function Word (span, duration, maxFontSize) {
this.span = span;
this.inAction = false;
this.duration = duration;
this.maxFontSize = maxFontSize;
}
/**
* #row {Numer} is a number of imaginary row to place the word into
* #callback {Function} to call on animation end
*/
Word.prototype.animate = function (row, callback) {
var self = this;
// Skip turn if the word is still busy in previous animation
if (self.inAction) {
return;
}
var start = null,
dur = self.duration,
mfs = self.maxFontSize,
top = row * mfs,
// Random left offset (in %)
left = Math.floor(Math.random() * 90),
// Vary then font size within half-max size and max size
fs = mfs - Math.floor(Math.random() * mfs / 2);
self.inAction = true;
self.span.style.top = top + 'px';
self.span.style.left = left + '%';
function step (timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
// Calculate the factor that will change from 0 to 1, then from 1 to 0 during the animation process
var factor = 1 - Math.sqrt(Math.pow(2 * Math.min(progress, dur) / dur - 1, 2));
self.span.style.fontSize = fs * factor + 'px';
if (progress < dur) {
window.requestAnimationFrame(step);
}
else {
self.inAction = false;
callback();
}
}
window.requestAnimationFrame(step);
}
pulsar.start();
body {
background: black;
color: white;
}
.word {
position: absolute;
text-shadow: 0 0 5px rgba(255, 255, 255, 0.9);
font-size: 0px;
/* To make height of the .word to equal it's font size */
line-height: 1;
}
<span class="word" data-pulsate="true">Love</span>
<span class="word" data-pulsate="true">Enjoy</span>
<span class="word" data-pulsate="true">Huggs</span>
<span class="word" data-pulsate="true">Peace</span>
I updated your plugin constructor. Notice the variable Pulsate.possiblePlaces, I have changed the variable declaration this way to be able to share variable data for all Object instance of your plugin.
var Pulsate = function(element) {
var self = this;
self.element = element;
self.max = 70;
self.min = 0;
self.speed = 500;
self.first = true;
self.currentPlace;
Pulsate.possiblePlaces = [
{
id: 0,
top: 150,
left: 150,
},
{
id: 1,
top: 250,
left: 250,
},
{
id: 2,
top: 350,
left: 350,
},
{
id: 3,
top: 250,
left: 750,
},
{
id: 4,
top: 450,
left: 950,
}
];
};
I added occupied attribute to the possible places to identify those that are already occupied. If the randomed currentPlace is already occupied, search for random place again.
Pulsate.prototype.defineRandomPlace = function() {
var self = this;
self.currentPlace = Pulsate.possiblePlaces[Math.floor(Math.random() * Pulsate.possiblePlaces.length)];
if(!Pulsate.possiblePlaces) self.defineRandomPlace;
if (!self.currentPlace.occupied) {
self.currentPlace.occupied = true;
self.element.css('top', self.currentPlace.top + 'px');
self.element.css('left', self.currentPlace.left + 'px');
} else {
self.defineRandomPlace();
}
};
Every time the element is hidden, set the occupied attribute to false.
Pulsate.prototype.animateToZero = function() {
var self = this;
self.element.animate({
'fontSize': 0,
'queue': true
}, self.speed, function() {
self.currentPlace.occupied = false;
self.defineRandomPlace();
});
};
A small hint f = 0.5px x = 100px t = 0.5s
x / f = 200
200/2 * t * 0.5 = f(shrink->expand until 100px square) per 0.5 seconds
I have a sentence where I fade in one word and replace it with another from an array. However, since the words all vary in length, the sentence width abruptly changes and it results in a choppy transition.
How can I animate the width change? I tried adding a transition to the container of the sentence in css but that didn't work. I applied the transition as 1.5s all linear, so it should be animating the width as well as everything else whenever there is change. Any ideas?
$(function() {
var hello = ['dynamic', 'a', 'aklsjdlfajklsdlkf', 'asdf'];
var used = ['dynamic'];
var greeting = $('#what');
var item;
function hey() {
item = hello[Math.floor(Math.random() * hello.length)];
if (hello.length != used.length) {
while (jQuery.inArray(item, used) != -1) {
item = hello[Math.floor(Math.random() * hello.length)];
}
used.push(item);
} else {
used.length = 0;
item = hello[Math.floor(Math.random() * hello.length)];
used.push(item);
}
greeting.html(item);
greeting.animate({
"opacity": "1"
}, 1500);
}
window.setInterval(function() {
greeting.animate({
"opacity": "0"
}, 1500);
setTimeout(hey, 1500)
}, 5000);
});
#sentence {
transition: 1.5s all linear;
}
#what {
font-style: italic;
text-decoration: underline;
color: red;
}
<p id="sentence">
This is a sentence that has <span id="what">dynamic</span> text that alters width.
</p>
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
EDIT: Sorry if I was unclear, I only want to fade out the word, not the entire sentence. I'm trying to animate the width to fit the new word. I don't want to change/add any elements, just solve with the current tags in place.
function dataWord () {
$("[data-words]").attr("data-words", function(i, d){
var $self = $(this),
$words = d.split("|"),
tot = $words.length,
c = 0;
// CREATE SPANS INSIDE SPAN
for(var i=0; i<tot; i++) $self.append($('<span/>',{text:$words[i]}));
// COLLECT WORDS AND HIDE
$words = $self.find("span").hide();
// ANIMATE AND LOOP
(function loop(){
$self.animate({ width: $words.eq( c ).width() });
$words.stop().fadeOut().eq(c).fadeIn().delay(1000).show(0, loop);
c = ++c % tot;
}());
});
}
// dataWord(); // If you don't use external fonts use this on DOM ready; otherwise use:
$(window).on("load", dataWord);
p{text-align: center;font-family: 'Open Sans Condensed', sans-serif;font-size: 2em;}
/* WORDS SWAP */
[data-words]{
vertical-align: top;
position: static;
}
[data-words] > span{
position: absolute;
color: chocolate;
}
<link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:300' rel='stylesheet' type='text/css'>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>
We provide
<span data-words="code|solutions|design"></span>
for your business.
</p>
<p>
You ordered
<span data-words="1|3|28"></span>
<b>big</b>
<span data-words="salad|macs|chips"></span>
</p>
When you set new word for your sentence, you can save #what width and then make an animation with this width too. Like this:
// declare as global variable and update when you set new word
var width = greeting.css('width');
// animation
greeting.animate({
"opacity": "0", "width": width
}, 1500, function(){
});
I have had the same problem and went with a different approach, not fading but typing: jsfiddle demo
function type($el, text, position) {
if (text.length >= position) {
var rchars = 'qbvol'; // typo chars
if (position % 3 == 0 && Math.random() > .85) { // insert typo!
var typo;
var chr = text.substr(position, 1);
if (chr == chr.toUpperCase()) { typo = chr.toLowerCase(); }
else { typo = rchars.substr(Math.floor(Math.random() * rchars.length), 1); }
$el.text(text.substring(0, position - 1) + typo + '_');
setTimeout(function() { type($el, text, position - 1); }, 200)
}
else {
$el.text(text.substring(0, position) + '_');
setTimeout(function() { type($el, text, position + 1); }, 150)
}
}
else {
setTimeout(function() { $el.text(text); }, 400)
}
}
It basically inserts your new text on the page, with a nice caret and typo to make it look like someone is typing it.
Try this out:- http://jsfiddle.net/adiioo7/c8fFU/13/
You can update the sentence effect depending upon your requirement. Currently it is using fadein/fadeout.
JS:-
$(function () {
var hello = ['jupiter', 'a', 'aklsjdlfajklsdlkf', 'asdf'];
var used = ['jupiter'];
var greeting = $('#what');
var item;
var sentence = $('#sentence');
function hey() {
item = hello[Math.floor(Math.random() * hello.length)];
if (hello.length != used.length) {
while (jQuery.inArray(item, used) != -1) {
item = hello[Math.floor(Math.random() * hello.length)];
}
used.push(item);
} else {
used.length = 0;
item = hello[Math.floor(Math.random() * hello.length)];
used.push(item);
}
greeting.html(item);
greeting.animate({
"opacity": "1"
}, 1500);
sentence.fadeIn(1500);
}
window.setInterval(function () {
sentence.fadeOut(1500);
greeting.animate({
"opacity": "0"
}, 1500);
setTimeout(hey, 1500);
}, 5000);
});
I am trying this code but i get: document.getElementsByName(...).style is undefined
I have also a problem with the delegation, i think. Any help?
<html>
<head>
<style type="text/css">
#toolTip {
position:relative;
width:200px;
margin-top:-90px;
}
#toolTip p {
padding:10px;
background-color:#f9f9f9;
border:solid 1px #a0c7ff;
-moz-border-radius:5px;-ie-border-radius:5px;-webkit-border-radius:5px;-o-border-radius:5px;border-radius:5px;
}
#tailShadow {
position:absolute;
bottom:-8px;
left:28px;
width:0;height:0;
border:solid 2px #fff;
box-shadow:0 0 10px 1px #555;
}
#tail1 {
position:absolute;
bottom:-20px;
left:20px;
width:0;height:0;
border-color:#a0c7ff transparent transparent transparent;
border-width:10px;
border-style:solid;
}
#tail2 {
position:absolute;
bottom:-18px;
left:20px;
width:0;height:0;
border-color:#f9f9f9 transparent transparent transparent;
border-width:10px;
border-style:solid;
}
</style>
<script type='text/javascript'>
function load () {
var elements = document.getElementsByName('toolTip');
for(var i=0; i<elements.length; i++) {
document.getElementsByName(elements[i]).style.visibility = 'hidden';
}
}
</script>
</head>
<body onload="load()">
<br><br><br><br><br><br><br><br><br><br><br><br>
<a class="hd"
onMouseOver="document.getElementsByName('toolTip')[0].style.visibility = 'visible'"
onmouseout ="document.getElementsByName('toolTip')[0].style.visibility = 'hidden'">aqui</a>
<div id="toolTip" name="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
<br><br><br>
<a class="hd"
onMouseOver="document.getElementsByName('toolTip')[0].style.visibility = 'visible'"
onmouseout ="document.getElementsByName('toolTip')[0].style.visibility = 'hidden'">aqui</a>
<div id="toolTip" name="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
</body>
</html>
demo
Try changing the id toolTip to a class:
<div class="toolTip">...</div>
And change your JS to use the display style-thing, rather than visibility, nd the onmouseover's are best dealt with using JS event delegation:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
mouseOver = function(e)
{//handler for mouseover
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;//gets the next element in DOM (ie the tooltip)
//check if mouse is over a relevant element:
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/))
{//nope? stop here, then
return e;
}
targetToolTip.style.display = 'block';//make visible
for (i=0;i<tooltips.length;i++)
{//closures are neat --> you have a reference to all tooltip elements from load scope
if (tooltips[i] !== targetToolTip)
{//not the one you need to see
tooltips[i].style.display = 'none';
}
}
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
//add listener:
if (document.body.addEventListener)
{//IE > 9, chrome, safari, FF...
document.body.addEventListener('mouseover',mouseOver,false);
}
else
{//IE8
document.body.attachEvent('onmouseover',mouseOver);
}
}
Google JavaScript event delegation and closures if this code isn't clear, but that's just how I would tackle this kind of thing. IMO, it's fairly efficient (you could use the closure scope to keep track of the tooltip that's currently visible and not loop through all of them, too, that would be even better:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
currentToolTip,//<-- reference currently visible
mouseOver = function(e)
{
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/) || targetToolTip === currentToolTip)
{//add check for currently visible TT, if so, no further action required
return e;
}
if (currentToolTip !== undefined)
{
currentToolTip.style.display = 'none';//hide currently visible
}
targetToolTip.style.display = 'block';//make new visible
currentToolTip = targetToolTip;//keep reference for next event
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
if (document.body.addEventListener)
{
document.body.addEventListener('mouseover',mouseOver,false);
}
else
{
document.body.attachEvent('onmouseover',mouseOver);
}
}
And you're there.
Edit:
To hide the tooltip on mouseout, you can either add a second listener directly:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
currentToolTip,//<-- reference currently visible
mouseOver = function(e)
{
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/) || targetToolTip === currentToolTip)
{//add check for currently visible TT, if so, no further action required
return e;
}
if (currentToolTip !== undefined)
{
currentToolTip.style.display = 'none';//hide currently visible
}
targetToolTip.style.display = 'block';//make new visible
currentToolTip = targetToolTip;//keep reference for next event
},
mouseOut = function(e)
{
e = e || window.event;
var movedTo = document.elementFromPoint(e.clientX,e.clientY);//check where the cursor is NOW
if (movedTo === curentToolTip || currentToolTip === undefined)
{//if cursor moved to tooltip, don't hide it, if nothing is visible, stop
return e;
}
currentTooltip.style.display = 'none';
currentTooltip = undefined;//no currentToolTip anymore
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
if (document.body.addEventListener)
{
document.body.addEventListener('mouseover',mouseOver,false);
document.body.addEventListener('mouseout',mouseOut,false);
}
else
{
document.body.attachEvent('onmouseover',mouseOver);
document.body.attachEvent('onmouseout',mouseOut);
}
}
Note, this is completely untested. I'm not entirely sure if IE < 9 supports elementFromPoint (gets the DOM element that is rendered at certain coordinates), or even if the IE event object has the clientX and clientY properties, but I figure a quick google will tell you more, including how to get the coordinates and the element that is to be found under the cursor in old, crummy, ghastly IE8, but this should help you on your way. Of course, if you don't want the contents of the tooltip to be selectable, just change the mouseOut function to:
mouseOut = function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (currentToolTip)
{
currentToolTip.style.diplay = 'none';
currentToolTip = undefined;
}
};
No need to check if the mouseout was on the correct element, just check if there is a current tooltip, and hide it.
Try using classes to mark the tooltips:
<div id="toolTip1" class="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
And JQuery to toggle the visibility using the class as the selector:
$('.toolTip').attr('visibility', 'hidden')
Definitely clean up the non-unique Id's - this will cause you no end of troubles otherwise
Your problem is likely because you're using the same id for both the tooltips. This is invalid; an id should be unique -- only one element in a given page should have a specific ID.
If you need a shared identifier for multiple objects, use a class instead.
I built a tooltip with a border in pure js that doesn't use hover.
html
<div id="infoId" class='info' style="font-variant:small-caps;text-align:center;padding-top:10px;">
<span id="innerspanid">
</span>
</div>
</div>
<input id="startbtn" class="getstartedbtn" type="button" value="Start >" />
</div>
js
function getTextWidth(text, font) {
// re-use canvas object for better performance
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
const context = canvas.getContext("2d");
context.font = font;
const metrics = context.measureText(text);
return metrics.width;
}
function getCssStyle(element, prop) {
return window.getComputedStyle(element, null).getPropertyValue(prop);
}
function getCanvasFontSize(el = document.body) {
const fontWeight = getCssStyle(el, 'font-weight') || 'normal';
const fontSize = getCssStyle(el, 'font-size') || '16px';
const fontFamily = getCssStyle(el, 'font-family') || 'Times New Roman';
return `${fontWeight} ${fontSize} ${fontFamily}`;
}
let arrowDimensionWidth = 20;
let arrowDimensionHeight = 20;
let tooltipTextHorizontalMargin = 50;
function openTooltip(text) {
let innerSpan = document.getElementById("innerspanid");
innerSpan.innerHTML = text;
let computedW = getTextWidth(text, getCanvasFontSize(innerSpan)) + tooltipTextHorizontalMargin;
let pointer = document.getElementById('pointer')
pointer.style.right = (((computedW / 2) - (arrowDimensionWidth / 2)) - (0)) + 'px';
let elem = document.getElementById('tooltipHost').parentNode.querySelector('div.info_container');
elem.style.left = ((tooltipHost.getBoundingClientRect().width - computedW) / 2) + "px";
elem.style.width = computedW + "px";
elem.style.display = 'block';
}
function buildTooltip() {
let elements = document.querySelectorAll('div.tooltip');
// Create a canvas element where the triangle will be drawn
let canvas = document.createElement('canvas');
canvas.width = arrowDimensionWidth; // arrow width
canvas.height = arrowDimensionHeight; // arrow height
let ctx = canvas.getContext('2d');
ctx.strokeStyle = 'darkred'; // Border color
ctx.fillStyle = 'white'; // background color
ctx.lineWidth = 1;
ctx.translate(-0.5, -0.5); // Move half pixel to make sharp lines
ctx.beginPath();
ctx.moveTo(1, canvas.height); // lower left corner
ctx.lineTo((canvas.width / 2), 1); // upper right corner
ctx.lineTo(canvas.width, canvas.height); // lower right corner
ctx.fill(); // fill the background
ctx.stroke(); // stroke it with border
ctx.fillRect(0, canvas.height - 0.5, canvas.width - 1, canvas.height + 2); //fix bottom row
// Create a div element where the triangle will be set as background
pointer = document.createElement('div');
pointer.id = "pointer"
pointer.style.width = canvas.width + 'px';
pointer.style.height = canvas.height + 'px';
pointer.innerHTML = ' ' // non breaking space
pointer.style.backgroundImage = 'url(' + canvas.toDataURL() + ')';
pointer.style.position = 'absolute';
pointer.style.top = '2px';
pointer.style.zIndex = '1'; // place it over the other elements
let idx;
let len;
for (idx = 0, len = elements.length; idx < len; ++idx) {
let elem = elements[idx];
let text = elem.querySelector('div.info');
let info = document.createElement('div');
text.parentNode.replaceChild(info, text);
info.className = 'info_container';
info.appendChild(pointer.cloneNode());
info.appendChild(text);
}
}
window.addEventListener('load', buildTooltip);
window.addEventListener('load', wireup);
function wireup() {
document.getElementById('startbtn').addEventListener('click', function (evt1) {
openTooltip("bad email no # sign");
return false;
});
}
css
div.tooltip {
position: relative;
display: inline-block;
}
div.tooltip > div.info {
display: none;
}
div.tooltip div.info_container {
position: absolute;
left: 0px;
width: 100px;
height: 70px;
display: none;
}
div.tooltip div.info {
position: absolute;
left: 0px;
text-align: left;
background-color: white;
font-size: 18px;
left: 1px;
right: 1px;
top: 20px;
bottom: 1px;
color: #000;
padding: 5px;
overflow: auto;
border: 1px solid darkred;
border-radius: 5px;
}