Related
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.
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
Please take a look at this fiddle: http://jsfiddle.net/dhcyA/
Try clicking on a block. What I want is that when the other elements disapear, the selected block will animate/ease to his giving position instead of just jumping like it does now. Then the same animation repeats itself when clicking again on the box, but then back to place.
Maybe to keep in mind:
I'm using a reponsive design, which means those blocks can be vertical and horizontal after scaling the window.
Any redevisions on the fiddle or suggustions would be great!
Here is my solution.
On your existing markup, I added a wrapper division to calculate the position of boxes inside the wrapper. Like this
<div id="wrapper">
<div class="block">
<h2>I'm block 1</h2>
</div>
....
</div>
To maintain the fluidness of the block, I created a function to position the block on the wrapper. Here is the function for position of the blocks:
var reposition = function() {
wrapper = $("#wrapper");
console.log(wrapper.innerWidth());
pLeft = 0;
pTop = 0;
maxRowHeight = 0;
$(".block").each(function(){
if($(this).data('active')) {
$(this).data('top', pTop);
$(this).data('left', pLeft);
} else {
$(this).stop(0,0).animate({
'top' : pTop + 'px',
'left' : pLeft + 'px'
});
}
pLeft += $(this).outerWidth() + parseInt($(this).css('marginLeft'));
if($(this).height() > maxRowHeight) maxRowHeight = $(this).outerHeight() + parseInt($(this).css('marginTop')); //Find out the longest block on the row
if(pLeft + $(this).next().outerWidth() + parseInt($(this).next().css('marginLeft')) >= wrapper.innerWidth()) {
pLeft = 0;
pTop += maxRowHeight;
maxRowHeight = 0;
}
});
};
Finally, the script to toggle the block
$(".block").click(function() {
$(this).siblings().slideToggle('slow'); //Toggle other blocks
if(!$(this).data('active')){ //if the block is not active
$(this).data('left', $(this).position().left); //sets its left
$(this).data('top', $(this).position().top); // and top position
$(this).animate({ //animate at the top and bottom
top:0,
left:0
},'slow');
$(this).data('active',true);
}else{
$(this).animate({ //animate to its last known position
top:$(this).data('top'),
left:$(this).data('left')
},'slow');
$(this).data('active',false);
}
});
Demos
Demo[Full] (Resize this to see the fluidness maintained)
Demo[Full] (version showing variable heights)
Here is what this solutions gives:
Remembers the last position and gradually animate to/from this position
Block positions are calculated and animated on load and every resize
Repositioning happens on $(window).resize() thus maintaining the fluid nature of the block, despite the use of position absolute
Support variable heights
Minor change on existing markup & CSS
Also fixed two issues extended by Gaby
Accounts for each block margin independently
Recalculates the position of the element after resize
Final Update
Here is a full working solution (pretty straight forward in my opinion) with JS to set the positioning (a simple calculation) and CSS transitions for the rest..
Demo at http://jsfiddle.net/gaby/pYdKB/3/
It maintains the fluidity of float:left and works with any number of elements, and you can keep the :nth-child for the styling, and it will also work if you want to leave more than one element visible..
javascript
var wrapper = $('.wrapper'),
boxes = wrapper.children(),
boxWidth = boxes.first().outerWidth(true),
boxHeight = boxes.first().outerHeight(true);
function rePosition(){
var w = wrapper.width(),
breakat = Math.floor( w / boxWidth ); // calculate fluid layout, just like float:left
boxes
.filter(':not(.go)')
.each(function(i){
var matrixX = ((i)%breakat)+1,
matrixY = Math.ceil((i+1)/breakat);
$(this).css({
left:(matrixX-1) * boxWidth ,
top: (matrixY-1) * boxHeight
});
});
}
$('.box').click(function(){
$(this)
.siblings()
.toggleClass('go');// just add the go class, and let CSS handle the rest
rePosition(); // recalculate final positions and let CSS animate the boxes
});
$(window).resize(rePosition);
$(window).trigger('resize');
CSS
.wrapper{
position:relative;
}
.box{
width:200px;
height:100px;
position:absolute;
margin:5px;
cursor:pointer;
overflow:hidden;
text-align: center;
line-height: 100px;
-moz-transition-property: top,left,width,height;
-webkit-transition-property: top,left,width,height;
-ms-transition-property: top,left,width,height;
-o-transition-property: top,left,width,height;
transition-property: top,left,width,height;
-moz-transition-duration: 1s;
-webkit-transition-duration: 1s;
-ms-transition-duration: 1s;
-o-transition-duration: 1s;
transition-duration: 1s;
}
.go{
height:0;
width:0;
}
note: As #Athari correctly mentioned in the comments, you should include all browser prefixes for the widest support. (my initial answer only included moz / webkit and the standard)
Original Answer
You can not do it directly with your current HTML structure. The floated concept does not support it.
But if you can afford an extra wrapper, then it is no problem..
Just slide the contents of your extra wrapper element..
Put the float code on the wrapper element and use
$(document).ready(function() {
$(".block-wrapper").click(function() {
$(this).siblings().find('.block').slideToggle("slow");
});
});
Demo at http://jsfiddle.net/gaby/t8GNP/
Update #1
If you need to move the clicked element to the top left and back, then you cannot really do it with CSS.
You will need to manually position them (through JS), set CSS transitions (or jquery), and apply the new positions once you click.
Later on you might want more than one to remain visible and reposition as well..
So you might want to take a look at the great Isotope plugin which can handle this and a multitude of more situations/layouts
Here is my version:
http://jsfiddle.net/selbh/dhcyA/92/
(only javascript is changed, and it's responsive)
$(document).ready(function() {
$(".block").click(function() {
var $this = $(this);
var pos = $this.offset();
var $siblings = $(this).siblings().add(this);
var marginTop = $this.css('marginTop').replace(/[^-\d\.]/g, '');
var marginLeft = $this.css('marginLeft').replace(/[^-\d\.]/g, '');
var $clone = $this.clone();
$siblings.slideToggle("slow");
$clone.css({
position: 'absolute',
left: pos.left - marginLeft,
top: pos.top - marginTop,
'background-color': $this.css('background-color')
});
$('body').append($clone);
$this.css('opacity', 0);
$clone.animate({
'left': 0,
'top': 0
});
$clone.click(function() {
$siblings.slideToggle("slow", function() {
$clone.remove();
$this.css('opacity', 1);
});
$clone.animate({
left: pos.left - marginLeft,
top: pos.top - marginTop
});
});
});
});
I'm kind of sleepy(It's 2:30 AM here) so I leave the half done answer here to give you an idea (I did it in 30 minutes so I guess with 30 minutes more you can get something really nice)
http://jsfiddle.net/LuL2s/2/
The trick comes by the block-holder which make the ease animation and making a difference between when they appear and disappear
JS
$(document).ready(function() {
var open = true;
$(".block").click(function() {
var $this = $(this);
var count = 0;
if (open) {
$this.parent().siblings().children().slideToggle("slow", function(){
if (count++ == 2) {
$this.parent().siblings().animate({width: 'toggle', height:'toggle'});
}
});
} else {
$this.parent().siblings().animate({width: 'toggle', height:'toggle'}, function(){
if (count++ == 2) {
$this.parent().siblings().children().slideToggle("slow");
}
});
}
open = !open;
});
});
HTML
<div class="block-holder">
<div class="block">
<h2>I'm block 1</h2>
</div>
</div>
<div class="block-holder">
<div class="block">
<h2>I'm block 2</h2>
</div>
</div>
<div class="block-holder">
<div class="block">
<h2>I'm block 3</h2>
</div>
</div>
<div class="block-holder">
<div class="block">
<h2>I'm block 4</h2>
</div>
</div>
CSS
.block {
width: 100%;
height: 100%;
text-align: center;
line-height: 100px;
cursor: pointer;
}
.block-holder:nth-child(1) .block {
background: green;
}
.block-holder:nth-child(2) .block {
background: red;
}
.block-holder:nth-child(3) .block {
background: orange;
}
.block-holder:nth-child(4) .block {
background: pink;
}
.block-holder {
width: 200px;
height: 100px;
float: left;
margin: 20px;
}
Great Challenge!
New Version:
Here is a much better version as it makes the blocks stay in their rows. I added a css function so that your nth-child styles could be applied even in the rows. Even maintains same HTML Structure.
Demo: http://jsfiddle.net/MadLittleMods/fDDZB/23/
The jQuery for this new revision looks like:
$('.block').on('click', function() {
var block = $(this);
// Keep the blocks in line
makeRows($('body'));
$('.block').not(this).each(function() {
// If sibling on the same level, horizontal toggle
// We also want ignore the toggleMethod if it is shown because we might need to reassign
if (($(this).position().top == block.position().top && (($(this).data('toggle') == -1) || $(this).data('toggle') == null)) || ($(this).data('toggle') != -1 && $(this).data('toggleMethod') == 'side'))
{
$(this).data('toggleMethod', 'side');
// Hide block
if ($(this).data('toggle') == -1 || $(this).data('toggle') == null)
{
// Set properties for later use in show block
$(this).data('overflowBefore', $(this).css('overflow'));
$(this).css('overflow', 'hidden');
$(this).data('marginBefore', $(this).css('margin'));
var width = $(this).width();
$(this).animate({
width: 0,
margin: 0
}, function() {
$(this).data('toggle', width);
});
}
// Show block
else
{
$(this).css('overflow', $(this).data('overflowBefore'));
$(this).animate({
width: $(this).data('toggle'),
margin: $(this).data('marginBefore')
}, function() {
$(this).data('toggle', -1);
});
}
}
// Do a normal vertical toggle
else
{
$(this).data('toggleMethod', 'top');
$(this).slideToggle('slow');
}
});
});
// Make rows to make the blocks in line
function makeRows(container)
{
// Make rows so that the elements stay where they should
var containerWidth = container.width();
var currentRowWidth = 0;
// Add styles first so nothing gets messed up
container.children().each(function() {
var itemCSS = css($(this));
$(this).css(itemCSS);
});
// Now assemble the rows
container.children().each(function() {
var blockWidth = $(this).outerWidth() + parseInt($(this).css('margin-left')) + parseInt($(this).css('margin-right'));
if((currentRowWidth + blockWidth) < containerWidth)
{
currentRowWidth += blockWidth;
}
else
{
Array.prototype.reverse.call($(this).prevUntil('.row')).wrapAll('<div class="row"></div>');
$(this).prev().append('<div class="row_clear" style="clear: both;"></div>');
currentRowWidth = 0;
}
});
}
// Remove the rows added
function deleteRows()
{
var content = $('.row').contents()
$('.row').replaceWith(content);
$('.row_clear').remove();
}
$(window).resize(function() {
deleteRows();
});
// Functions courtesy of marknadal
// https://stackoverflow.com/a/5830517/796832
function css(a)
{
var sheets = document.styleSheets, o = {};
for(var i in sheets) {
var rules = sheets[i].rules || sheets[i].cssRules;
for(var r in rules) {
if(a.is(rules[r].selectorText)) {
o = $.extend(o, css2json(rules[r].style), css2json(a.attr('style')));
}
}
}
return o;
}
function css2json(css)
{
var s = {};
if(!css) return s;
if(css instanceof CSSStyleDeclaration) {
for(var i in css) {
if((css[i]).toLowerCase) {
s[(css[i]).toLowerCase()] = (css[css[i]]);
}
}
} else if(typeof css == "string") {
css = css.split("; ");
for (var i in css) {
var l = css[i].split(": ");
s[l[0].toLowerCase()] = (l[1]);
};
}
return s;
}
I added a makeRows and deleteRows functions so that the blocks would stay in their rows instead of getting smaller and moving into the row above. I call deleteRows whenever the window resizes so that it can maintain a responsive layout. Then if the blocks need to be toggled, I recreate the rows.
css and css2json functions are courtesy of marknadal
Old version:
I came up with a solution with .animate so that it could ease horizontally.
Here is a demo: http://jsfiddle.net/MadLittleMods/fDDZB/8/
The jQuery looks like:
$('.block').on('click', function() {
var block = $(this);
$(this).siblings().each(function() {
// If sibling on the same level, horizontal toggle
// We also want ignore the toggleMethod if it is shown because we might need to reassign
if (($(this).position().top == block.position().top && ($(this).data('toggle') == -1) || $(this).data('toggle') == null) || ($(this).data('toggle') != -1 && $(this).data('toggleMethod') == 'side'))
{
$(this).data('toggleMethod', 'side');
// Hide block
if ($(this).data('toggle') == -1 || $(this).data('toggle') == null)
{
// Set properties for later use in show block
$(this).data('overflowBefore', $(this).css('overflow'));
$(this).css('overflow', 'hidden');
$(this).data('marginBefore', $(this).css('margin'));
var width = $(this).width();
$(this).animate({
width: 0,
margin: 0
}, function() {
$(this).data('toggle', width);
});
}
// Show block
else
{
$(this).css('overflow', $(this).data('overflowBefore'));
$(this).animate({
width: $(this).data('toggle'),
margin: $(this).data('marginBefore')
}, function() {
$(this).data('toggle', -1);
});
}
}
// Do a normal vertical toggle
else
{
$(this).data('toggleMethod', 'top');
$(this).slideToggle('slow');
}
});
});
The key was to separate the blocks that were toggled with .slideToggle and .animate because you have to apply the same when they show and hide.
I'm trying to find a very simple and smooth, lightweight javascript or jquery marquee. I already tried silk marquee or something, but it wouldn't work with the application I was using. So the simpler and shorter, the better - and easier to debug. Does anybody know of a easy to implement javascript replacement for the marquee?
Pastebin
Code
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript">
var tWidth='300px'; // width (in pixels)
var tHeight='25px'; // height (in pixels)
var tcolour='#ffffcc'; // background colour:
var moStop=true; // pause on mouseover (true or false)
var fontfamily = 'arial,sans-serif'; // font for content
var tSpeed=3; // scroll speed (1 = slow, 5 = fast)
// enter your ticker content here (use \/ and \' in place of / and ' respectively)
var content='Are you looking for loads of useful information <a href="http:\/\/javascript.about.com\/">About Javascript<\/a>? Well now you\'ve found it.';
var cps=-tSpeed; var aw, mq; var fsz = parseInt(tHeight) - 4; function startticker(){if (document.getElementById) {var tick = '<div style="position:relative;width:'+tWidth+';height:'+tHeight+';overflow:hidden;background-color:'+tcolour+'"'; if (moStop) tick += ' onmouseover="cps=0" onmouseout="cps=-tSpeed"'; tick +='><div id="mq" style="position:absolute;right:0px;top:0px;font-family:'+fontfamily+';font-size:'+fsz+'px;white-space:nowrap;"><\/div><\/div>'; document.getElementById('ticker').innerHTML = tick; mq = document.getElementById("mq"); mq.style.right=(10+parseInt(tWidth))+"px"; mq.innerHTML='<span id="tx">'+content+'<\/span>'; aw = document.getElementById("tx").offsetWidth; lefttime=setInterval("scrollticker()",50);}} function scrollticker(){mq.style.right = (parseInt(mq.style.right)>(-10 - aw)) ?
mq.style.right = parseInt(mq.style.right)+cps+"px": parseInt(tWidth)+10+"px";} window.onload=startticker;
</script>
</head>
<body>
<div id="ticker">
this is a simple scrolling text!
</div>
</body>
</html>
hiya simple demo from recommendations in above comments: http://jsfiddle.net/FWWEn/
with pause functionality on mouseover: http://jsfiddle.net/zrW5q/
hope this helps, have a nice one, cheers!
html
<h1>Hello World!</h1>
<h2>I'll marquee twice</h2>
<h3>I go fast!</h3>
<h4>Left to right</h4>
<h5>I'll defer that question</h5>
Jquery code
(function($) {
$.fn.textWidth = function(){
var calc = '<span style="display:none">' + $(this).text() + '</span>';
$('body').append(calc);
var width = $('body').find('span:last').width();
$('body').find('span:last').remove();
return width;
};
$.fn.marquee = function(args) {
var that = $(this);
var textWidth = that.textWidth(),
offset = that.width(),
width = offset,
css = {
'text-indent' : that.css('text-indent'),
'overflow' : that.css('overflow'),
'white-space' : that.css('white-space')
},
marqueeCss = {
'text-indent' : width,
'overflow' : 'hidden',
'white-space' : 'nowrap'
},
args = $.extend(true, { count: -1, speed: 1e1, leftToRight: false }, args),
i = 0,
stop = textWidth*-1,
dfd = $.Deferred();
function go() {
if(!that.length) return dfd.reject();
if(width == stop) {
i++;
if(i == args.count) {
that.css(css);
return dfd.resolve();
}
if(args.leftToRight) {
width = textWidth*-1;
} else {
width = offset;
}
}
that.css('text-indent', width + 'px');
if(args.leftToRight) {
width++;
} else {
width--;
}
setTimeout(go, args.speed);
};
if(args.leftToRight) {
width = textWidth*-1;
width++;
stop = offset;
} else {
width--;
}
that.css(marqueeCss);
go();
return dfd.promise();
};
})(jQuery);
$('h1').marquee();
$('h2').marquee({ count: 2 });
$('h3').marquee({ speed: 5 });
$('h4').marquee({ leftToRight: true });
$('h5').marquee({ count: 1, speed: 2 }).done(function() { $('h5').css('color', '#f00'); })
I've made very simple function for marquee. See: http://jsfiddle.net/vivekw/pHNpk/2/
It pauses on mouseover & resumes on mouseleave. Speed can be varied. Easy to understand.
function marquee(a, b) {
var width = b.width();
var start_pos = a.width();
var end_pos = -width;
function scroll() {
if (b.position().left <= -width) {
b.css('left', start_pos);
scroll();
}
else {
time = (parseInt(b.position().left, 10) - end_pos) *
(10000 / (start_pos - end_pos)); // Increase or decrease speed by changing value 10000
b.animate({
'left': -width
}, time, 'linear', function() {
scroll();
});
}
}
b.css({
'width': width,
'left': start_pos
});
scroll(a, b);
b.mouseenter(function() { // Remove these lines
b.stop(); //
b.clearQueue(); // if you don't want
}); //
b.mouseleave(function() { // marquee to pause
scroll(a, b); //
}); // on mouse over
}
$(document).ready(function() {
marquee($('#display'), $('#text')); //Enter name of container element & marquee element
});
I just created a simple jQuery plugin for that. Try it ;)
https://github.com/aamirafridi/jQuery.Marquee
The following works:
http://jsfiddle.net/xAGRJ/4/
The problem with your original code was you are calling scrollticker() by passing a string to setInterval, where you should just pass the function name and treat it as a variable:
lefttime = setInterval(scrollticker, 50);
instead of
lefttime = setInterval("scrollticker()", 50);
Why write custom jQuery code for Marquee... just use a plugin for jQuery - marquee() and use it like in the example below:
First include :
<script type='text/javascript' src='//cdn.jsdelivr.net/jquery.marquee/1.3.1/jquery.marquee.min.js'></script>
and then:
//proporcional speed counter (for responsive/fluid use)
var widths = $('.marquee').width()
var duration = widths * 7;
$('.marquee').marquee({
//speed in milliseconds of the marquee
duration: duration, // for responsive/fluid use
//duration: 8000, // for fixed container
//gap in pixels between the tickers
gap: $('.marquee').width(),
//time in milliseconds before the marquee will start animating
delayBeforeStart: 0,
//'left' or 'right'
direction: 'left',
//true or false - should the marquee be duplicated to show an effect of continues flow
duplicated: true
});
If you can make it simpler and better I dare you all people :). Don't make your life more difficult than it should be. More about this plugin and its functionalities at: http://aamirafridi.com/jquery/jquery-marquee-plugin
I made my own version, based in the code presented above by #Tats_innit .
The difference is the pause function. Works a little better in that aspect.
(function ($) {
var timeVar, width=0;
$.fn.textWidth = function () {
var calc = '<span style="display:none">' + $(this).text() + '</span>';
$('body').append(calc);
var width = $('body').find('span:last').width();
$('body').find('span:last').remove();
return width;
};
$.fn.marquee = function (args) {
var that = $(this);
if (width == 0) { width = that.width(); };
var textWidth = that.textWidth(), offset = that.width(), i = 0, stop = textWidth * -1, dfd = $.Deferred(),
css = {
'text-indent': that.css('text-indent'),
'overflow': that.css('overflow'),
'white-space': that.css('white-space')
},
marqueeCss = {
'text-indent': width,
'overflow': 'hidden',
'white-space': 'nowrap'
},
args = $.extend(true, { count: -1, speed: 1e1, leftToRight: false, pause: false }, args);
function go() {
if (!that.length) return dfd.reject();
if (width <= stop) {
i++;
if (i <= args.count) {
that.css(css);
return dfd.resolve();
}
if (args.leftToRight) {
width = textWidth * -1;
} else {
width = offset;
}
}
that.css('text-indent', width + 'px');
if (args.leftToRight) {
width++;
} else {
width=width-2;
}
if (args.pause == false) { timeVar = setTimeout(function () { go() }, args.speed); };
if (args.pause == true) { clearTimeout(timeVar); };
};
if (args.leftToRight) {
width = textWidth * -1;
width++;
stop = offset;
} else {
width--;
}
that.css(marqueeCss);
timeVar = setTimeout(function () { go() }, 100);
return dfd.promise();
};
})(jQuery);
usage:
for start: $('#Text1').marquee()
pause: $('#Text1').marquee({ pause: true })
resume: $('#Text1').marquee({ pause: false })
My text marquee for more text,
and position absolute enabled
http://jsfiddle.net/zrW5q/2075/
(function($) {
$.fn.textWidth = function() {
var calc = document.createElement('span');
$(calc).text($(this).text());
$(calc).css({
position: 'absolute',
visibility: 'hidden',
height: 'auto',
width: 'auto',
'white-space': 'nowrap'
});
$('body').append(calc);
var width = $(calc).width();
$(calc).remove();
return width;
};
$.fn.marquee = function(args) {
var that = $(this);
var textWidth = that.textWidth(),
offset = that.width(),
width = offset,
css = {
'text-indent': that.css('text-indent'),
'overflow': that.css('overflow'),
'white-space': that.css('white-space')
},
marqueeCss = {
'text-indent': width,
'overflow': 'hidden',
'white-space': 'nowrap'
},
args = $.extend(true, {
count: -1,
speed: 1e1,
leftToRight: false
}, args),
i = 0,
stop = textWidth * -1,
dfd = $.Deferred();
function go() {
if (that.css('overflow') != "hidden") {
that.css('text-indent', width + 'px');
return false;
}
if (!that.length) return dfd.reject();
if (width <= stop) {
i++;
if (i == args.count) {
that.css(css);
return dfd.resolve();
}
if (args.leftToRight) {
width = textWidth * -1;
} else {
width = offset;
}
}
that.css('text-indent', width + 'px');
if (args.leftToRight) {
width++;
} else {
width--;
}
setTimeout(go, args.speed);
};
if (args.leftToRight) {
width = textWidth * -1;
width++;
stop = offset;
} else {
width--;
}
that.css(marqueeCss);
go();
return dfd.promise();
};
// $('h1').marquee();
$("h1").marquee();
$("h1").mouseover(function () {
$(this).removeAttr("style");
}).mouseout(function () {
$(this).marquee();
});
})(jQuery);
Responsive resist jQuery marquee simple plugin. Tutorial:
// start plugin
(function($){
$.fn.marque = function(options, callback){
// check callback
if(typeof callback == 'function'){
callback.call(this);
} else{
console.log("second argument (callback) is not a function");
// throw "callback must be a function"; //only if callback for some reason is required
// return this; //only if callback for some reason is required
}
//set and overwrite default functions
var defOptions = $.extend({
speedPixelsInOneSecound: 150, //speed will behave same for different screen where duration will be different for each size of the screen
select: $('.message div'),
clickSelect: '', // selector that on click will redirect user ... (optional)
clickUrl: '' //... to this url. (optional)
}, options);
//Run marque plugin
var windowWidth = $(window).width();
var textWidth = defOptions.select.outerWidth();
var duration = (windowWidth + textWidth) * 1000 / defOptions.speedPixelsInOneSecound;
var startingPosition = (windowWidth + textWidth);
var curentPosition = (windowWidth + textWidth);
var speedProportionToLocation = curentPosition / startingPosition;
defOptions.select.css({'right': -(textWidth)});
defOptions.select.show();
var animation;
function marquee(animation){
curentPosition = (windowWidth + defOptions.select.outerWidth());
speedProportionToLocation = curentPosition / startingPosition;
animation = defOptions.select.animate({'right': windowWidth+'px'}, duration * speedProportionToLocation, "linear", function(){
defOptions.select.css({'right': -(textWidth)});
});
}
var play = setInterval(marquee, 200);
//add onclick behaviour
if(defOptions.clickSelect != '' && defOptions.clickUrl != ''){
defOptions.clickSelect.click(function(){
window.location.href = defOptions.clickUrl;
});
}
return this;
};
}(jQuery));
// end plugin
Use this custom jQuery plugin as bellow:
//use example
$(window).marque({
speedPixelsInOneSecound: 150, // spped pixels/secound
select: $('.message div'), // select an object on which you want to apply marquee effects.
clickSelect: $('.message'), // select clicable object (optional)
clickUrl: 'services.php' // define redirection url (optional)
});
Marquee using CSS animations.
`<style>
.items-holder {
animation: moveSlideshow 5s linear infinite;
}
.items-holder:hover {
animation-play-state: paused;
}
#keyframes moveSlideshow {
100% {
transform: translateX(100%);
}
}
</style>`
I try use only css for it this link.
<style>
.header {
background: #212121;
overflow: hidden;
height: 65px;
position: relative;
}
.header div {
display: flex;
flex-direction: row;
align-items: center;
overflow: hidden;
height: 65px;
transform: translate(100%, 0);
}
.header div * {
font-family: "Roboto", sans-serif;
color: #fff339;
text-transform: uppercase;
text-decoration: none;
}
.header div img {
height: 60px;
margin-right: 20px;
}
.header .ticker-wrapper__container{
display: flex;
flex-direction: row;
align-items: center;
position: absolute;
top: 0;
right: 0;
animation: ticker 30s infinite linear forwards;
}
.header:hover .ticker-wrapper__container{
animation-play-state: paused;
}
.ticker-wrapper__container a{
display: flex;
margin-right: 60px;
align-items: center;
}
#keyframes ticker {
0% {
transform: translate(100%, 0);
}
50% {
transform: translate(0, 0);
}
100% {
transform: translate(-100%, 0);
}
}
</style>
What is the most elegant way of showing an html text letter by letter (like videogame captions) using CSS and JavaScript?
While I'm sure that his can be solved using a brute-force approach (say, splitting the characters and print them one by one using jQuery.append()), I'm hoping there's some CSS3 (pseudo-elements?) or JQuery magic to do this more elegantly.
Extra points if the solution considers inner HTML content.
HTML
<div id="msg"/>
Javascript
var showText = function (target, message, index, interval) {
if (index < message.length) {
$(target).append(message[index++]);
setTimeout(function () { showText(target, message, index, interval); }, interval);
}
}
Call with:
$(function () {
showText("#msg", "Hello, World!", 0, 500);
});
If a smooth reveal is reasonable then I think this should be pretty straightforward. Untested, but this is how I imagine it would work
html
<div id="text"><span>The intergalactic space agency</span></div>
css
div#text { width: 0px; height: 2em; white-space: nowrap; overflow: hidden; }
jQuery
var spanWidth = $('#test span').width();
$('#text').animate( { width: spanWidth }, 1000 );
Okay, I couldn't resist and made a fiddle. One little code error that I fixed. Looks good to me though!
http://jsfiddle.net/mrtsherman/6qQrN/1/
100% vanilla javascript, strict mode, unobtrusive html,
function printLetterByLetter(destination, message, speed){
var i = 0;
var interval = setInterval(function(){
document.getElementById(destination).innerHTML += message.charAt(i);
i++;
if (i > message.length){
clearInterval(interval);
}
}, speed);
}
printLetterByLetter("someElement", "Hello world, bonjour le monde.", 100);
You really should just append, or show/hide.
However, if for some odd reason you don't want to alter your text, you can use this overly-complicated-for-no-good-reason piece of code:
HTML:
<p>I'm moving slowly...<span class="cover"></span></p>
CSS:
p {
font-family: monospace;
float: left;
padding: 0;
position: relative;
}
.cover {
height: 100%;
width: 100%;
background: #fff;
position: absolute;
top: 0;
right: 0;
}
jQuery:
var $p = $('p'),
$cover = $('.cover'),
width = $p.width(),
decrement = width / $p.text().length;
function addChar()
{
$cover.css('width', '-=' + decrement);
if ( parseInt( $cover.css('width') ) < width )
{
setTimeout(addChar, 300);
}
}
addChar();
And finally, here's the fiddle: http://jsfiddle.net/dDGVH/236/
But, seriously, don't use this...
Make your code more elegant by preparing promises for each iteration, then execute them as second step where you can inject DOM logic.
const message = 'Solution using Promises';
const typingPromises = (message, timeout) =>
[...message].map(
(ch, i) =>
new Promise(resolve => {
setTimeout(() => {
resolve(message.substring(0, i + 1));
}, timeout * i);
})
);
typingPromises(message, 140).forEach(promise => {
promise.then(portion => {
document.querySelector('p').innerHTML = portion;
});
});
<div ><p></p></div>
This is the React version using hooks.
import React, { useState, useEffect } from "react";
import "./styles.css";
const App = ({ speed, msg }) => {
const Typer = ({ speed = 250, children = " Introduce your text" }) =>
{
const [idx, setidx] = useState(0);
useEffect(() => {
const timer = window.setInterval(() => setidx((v) => v + 1), speed);
return () => window.clearInterval(timer);
});
return <div>{children.substr(0, idx)}</div>;
};
return (
<div>
<Typer speed={speed} children={msg}></Typer>
</div>
);
};
export default App;
and the main index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
const message2 = "This is some text to get typed on time";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<App speed={100} msg={message2} />
</StrictMode>,
rootElement
);
Have a look at code sandbox
When I did this I ran into the problem of a word jumping from the end of one line to the begging of the next as it the letters appeared to get around this I used to side by side spans, one of which the text was transparent, the other visible and simply moved letters one by one from the invisible span to the visible. Here's a fiddle.
HTML
<div class='wrapper'>
<span class='visible'></span><span class='invisible'></span>
</div>
CSS
.visible {
color: black;
}
.invisible {
color: transparent;
}
JS
var text = "Whatever you want your text to be here",
soFar = "";
var visible = document.querySelector(".visible"),
invisible = document.querySelector(".invisible");
invisible.innerHTML = text;
var t = setInterval(function(){
soFar += text.substr(0, 1),
text = text.substr(1);
visible.innerHTML = soFar;
invisible.innerHTML = text;
if (text.length === 0) clearInterval(t);
}, 100)
I made a tiny jquery plugin for that. First you need to make sure that the text will be visible if javascript is disabled, and if not, redisplay the text letter by letter.
$.fn.retype = function(delay) {
var el = this,
t = el.text(),
c = t.split(''),
l = c.length,
i = 0;
delay = delay || 100;
el.empty();
var interval = setInterval(function(){
if(i < l) el.text(el.text() + c[i++]);
else clearInterval(interval);
}, delay);
};
Usage will be just as easy as this:
$('h1').retype();
This is based on armen.shimoon's:
var showText = function (target, message, index, interval) {
if (index <= message.length && $(target).is(':visible')) {
$(target).html(message.substr(0, index++));
setTimeout(function () { showText(target, message, index, interval); }, interval);
}
}
message[index++] wasn't working in my jquery webpage - I had to change it to substr. Also my original text uses HTML and the text that is typed uses HTML formatting (br, b, etc). I've also got the function stopping if the target has been hidden.
Vanilla JavaScript version of #armen.shimoon's answer:
document.addEventListener('DOMContentLoaded', function() {
showText("#msg", "Hello, World!", 0, 100);
});
let showText = function (target, message, index, interval) {
if (index < message.length) {
document.querySelector(target).innerHTML =
document.querySelector(target).innerHTML + message[index++];
setTimeout(function () { showText(target, message, index, interval); }, interval);
}
}
<div id="msg"></div>
<span id="text_target"></span>
You need to wrap each letter in span tag, because anonymous html elements cannot be styled. Then reveal one span at a time. This avoids some innerText / innerHTML issues (no DOM reflow?) but can be overkill in your case.
Vanillla
(function () {
var showText = function(target, msg, index, interval){
var el = document.getElementById(target);
if(index < msg.length){
el.innerHTML = el.innerHTML + msg.charAt(index);
index = index + 1;
setTimeout(function(){
showText(target,msg,index,interval);
},interval);
}
};
showText("id", "Hello, World!", 0, 50);
})();
you could improve on this code by changing it so you only get the el one time due to the fact that it takes a bit of resources to modify the DOM.
there is a good answer how to do it here:
this is a way that you can manipulate each letter with any .animate() property available, not with hacks like covering the text with s etc.
I was trying to solve the same problem and I came up with this solution that seems to work.
HTML
<div id='target'></div>
jQuery
$(function() {
var message = 'Hello world';
var index = 0;
function displayLetter() {
if (index < message.length) {
$('#target').append(message[index++]);
}
else{
clearInterval(repeat);
}
}
var repeat = setInterval(displayLetter, 100);
});
There are a couple libraries for this now. There are TypeItJs and TypedJS both of them allow for html to be in the thing that you are typing out as well as numerous other methods that assist in animating Typewriter effects.
The answer I will type is the answer Show text letter by letter , but I made some changes to it
HTML
<h1>
<span id="msg"></span>
</h1>
Javacript
var showText = function (target, message, index, interval) {
if (index < message.length) {
//$(target).append(message[index++]);
$(target).text($(target).text() + message[index++]);
setTimeout(function () { showText(target, message, index, interval); }, interval);
}
}
$(function () {
showText("#msg", "Hello, World!", 0, 80);
//showText("#msg", "Hello, World!", 0, 500);
});
CSS
#msg {
color: #6d67c6;
font-family: sans-serif;
font-size: 30px;
font-weight: bold;
}
function textRoll(message, interval){
setTimeout(() => {
para.innerHTML += message.charAt(0);
return textRoll(message.slice(1))
}, interval)
}
try this with jquery
var spanWidth = $('#text span').width();
$('#text').animate( { width: spanWidth }, 3000 );
div#text { width: 0px; height: 2em; white-space: nowrap; overflow: hidden; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="text"><span>Hello World !</span></div>