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
Related
I've been working on a side project to recreate diablo / tarkov like inventory screens with pure html, css, and js. I'm attempting to use the draggable api's built into html for this but am hitting a blocker. Everything working fine for 1x1 wide/high cell's / items. The problem begins when trying to add an item that is longer in width and/or height. I can still drag around the item but I can't get it to do two things.
Cover all the cells correctly so there is no bleed through from the cells background.
Stop the item from being placed in a cell that would cover another item.
My ideal solution modifies either the linked codepen below or provides direction / a solution on a more appropriate approach whatever that may be. The only constraint is that it must use html, css, and javascript. The only exception is jQuery.
The Code
var draggedItem = null;
function Inventory(options) {
// Setup Rows and Items
this.init = function (options) {
let slotSizeW = options.size.w / options.slots.w;
let slotSizeH = options.size.h / options.slots.h;
this.html = { inventory: options.selector };
// Build grid
let grid = document.createElement("grid");
grid.style.width = options.size.w + "px";
grid.style.height = options.size.h + "px";
grid.style.gridTemplateColumns = "1fr ".repeat(options.slots.w);
grid.style.gridTemplateRows = "1fr".repeat(options.slots.h);
grid.cells = [];
for (var i = 0; i < options.slots.w * options.slots.h; i++) {
var cell = document.createElement("cell");
cell.style.width = slotSizeW + "px";
cell.style.height = slotSizeH + "px";
grid.appendChild(cell);
grid.cells.push(cell);
}
this.html.grid = grid;
this.html.inventory.appendChild(this.html.grid);
// Add all items
let items = [];
options.items.forEach(function (item, index, array) {
items.push(item);
let itemEl = document.createElement("item");
itemEl.setAttribute("draggable", true);
itemEl.style.background = item.color;
itemEl.style.width = 100 * item.w + "%";
itemEl.style.height = 100 * item.h + "%";
grid.cells[options.slots.w * item.x + item.y].appendChild(itemEl);
});
this.html.items = items;
let itemTags = document.querySelectorAll("item");
for (let i = 0; i < itemTags.length; i++) {
itemTags[i].addEventListener("dragstart", this.dragStart);
itemTags[i].addEventListener("dragend", this.dragEnd);
}
let cellTags = document.querySelectorAll("cell");
for (let i = 0; i < cellTags.length; i++) {
cellTags[i].addEventListener("dragover", this.dragOver);
cellTags[i].addEventListener("dragenter", this.dragEnter);
cellTags[i].addEventListener("dragleave", this.dragLeave);
cellTags[i].addEventListener("drop", this.dragDrop);
}
};
this.dragStart = function () {
this.classList.toggle("hold");
window.requestAnimationFrame(() => this.classList.toggle("invisible"));
draggedItem = this;
};
this.dragEnd = function () {
this.classList.toggle("hold");
this.classList.toggle("invisible");
};
this.dragOver = function (e) {
e.preventDefault();
};
this.dragEnter = function (e) {
e.preventDefault();
this.classList.toggle("hovered");
};
this.dragLeave = function (e) {
this.classList.remove("hovered");
};
this.dragDrop = function (e) {
this.classList.remove("hovered");
if (draggedItem != null) {
draggedItem.parentElement.removeChild(draggedItem);
this.appendChild(draggedItem);
}
};
this.init(options);
}
var inventory = new Inventory({
selector: document.querySelector("inventory"),
size: { w: 300, h: 300 },
slots: { w: 4, h: 4 },
items: [
{ x: 0, y: 0, w: 2, h: 2, content: "2x2", color: "#ffd54f" },
{ x: 2, y: 0, w: 1, h: 2, content: "1x2", color: "#66bb6a" },
{ x: 3, y: 3, w: 1, h: 1, content: "1x1", color: "#e53935" }
]
});
html,
body {
background: darksalmon;
}
grid {
position: relative;
display: grid;
}
cell {
position: relative;
border: 3px salmon solid;
background-color: white;
}
item {
display: block;
position: relative;
color: white;
cursor: pointer;
z-index: 2;
}
.hold {
border: solid #ccc 4px;
}
.hovered {
background: #f4f4f4;
border-style: dashed;
}
.invisible {
display: none;
}
<container>
<inventory>
</inventory>
</container>
To get each item's color to fill the underlying cells you have to take into account the extra width of each cell created by its border.
This snippet sets the width and height of an item using this formula:
itemEl.style.width = "calc((100% + 6px) * " + item.w + " - 4.9px)";
itemEl.style.height = "calc((100% + 6px) * " + item.h + " - 4.9px)";
This says add on an allowance for all 4 borders but remove the width of the outer borders. However, there can be a 'quirk' where the calculation the system has to do to map CSS pixels to screen pixels (several screen pixels may be used for one CSS pixel in modern displays) results in the odd screen pixel being left behind - in this case resulting in a thin white line around the item's color.
The item is made slightly wider (by just over 1 CSS pixel) and is placed half a pixel up and left which, at least in the tests I did with Edge/Chrome Windows10 removed this white 'border' at all zoom levels.
var draggedItem = null;
function Inventory(options) {
// Setup Rows and Items
this.init = function (options) {
let slotSizeW = options.size.w / options.slots.w;
let slotSizeH = options.size.h / options.slots.h;
this.html = { inventory: options.selector };
// Build grid
let grid = document.createElement("grid");
grid.style.width = options.size.w + "px";
grid.style.height = options.size.h + "px";
grid.style.gridTemplateColumns = "1fr ".repeat(options.slots.w);
grid.style.gridTemplateRows = "1fr".repeat(options.slots.h);
grid.cells = [];
for (var i = 0; i < options.slots.w * options.slots.h; i++) {
var cell = document.createElement("cell");
cell.style.width = slotSizeW + "px";
cell.style.height = slotSizeH + "px";
grid.appendChild(cell);
grid.cells.push(cell);
}
this.html.grid = grid;
this.html.inventory.appendChild(this.html.grid);
// Add all items
let items = [];
options.items.forEach(function (item, index, array) {
items.push(item);
let itemEl = document.createElement("item");
itemEl.setAttribute("draggable", true);
itemEl.style.background = item.color;
//itemEl.style.width = 100 * item.w + "%";
//itemEl.style.height = 100 * item.h + "%";
itemEl.style.width = "calc((100% + 6px) * " + item.w + " - 4.9px)";
itemEl.style.height = "calc((100% + 6px) * " + item.h + " - 4.9px)";
grid.cells[options.slots.w * item.x + item.y].appendChild(itemEl);
});
this.html.items = items;
let itemTags = document.querySelectorAll("item");
for (let i = 0; i < itemTags.length; i++) {
itemTags[i].addEventListener("dragstart", this.dragStart);
itemTags[i].addEventListener("dragend", this.dragEnd);
}
let cellTags = document.querySelectorAll("cell");
for (let i = 0; i < cellTags.length; i++) {
cellTags[i].addEventListener("dragover", this.dragOver);
cellTags[i].addEventListener("dragenter", this.dragEnter);
cellTags[i].addEventListener("dragleave", this.dragLeave);
cellTags[i].addEventListener("drop", this.dragDrop);
}
};
this.dragStart = function () {
this.classList.toggle("hold");
window.requestAnimationFrame(() => this.classList.toggle("invisible"));
draggedItem = this;
};
this.dragEnd = function () {
this.classList.toggle("hold");
this.classList.toggle("invisible");
};
this.dragOver = function (e) {
e.preventDefault();
};
this.dragEnter = function (e) {
e.preventDefault();
this.classList.toggle("hovered");
};
this.dragLeave = function (e) {
this.classList.remove("hovered");
};
this.dragDrop = function (e) {
this.classList.remove("hovered");
if (draggedItem != null) {
draggedItem.parentElement.removeChild(draggedItem);
this.appendChild(draggedItem);
}
};
this.init(options);
}
var inventory = new Inventory({
selector: document.querySelector("inventory"),
size: { w: 300, h: 300 },
slots: { w: 4, h: 4 },
items: [
{ x: 0, y: 0, w: 2, h: 2, content: "2x2", color: "#ffd54f" },
{ x: 2, y: 0, w: 1, h: 2, content: "1x2", color: "#66bb6a" },
{ x: 3, y: 3, w: 1, h: 1, content: "1x1", color: "#e53935" }
]
});
html,
body {
background: darksalmon;
}
grid {
position: relative;
display: grid;
}
cell {
position: relative;
border: 3px salmon solid;
background-color: white;
}
item {
display: block;
position: relative;
color: white;
cursor: pointer;
z-index: 2;
top: -0.5px;
left: -0.5px;
}
.hold {
border: solid #ccc 4px;
}
.hovered {
background: #f4f4f4;
border-style: dashed;
}
.invisible {
display: none;
}
<container>
<inventory>
</inventory>
</container>
For the second part of the question on a drop you will have to loop through the items to see if any overlaps with the dragged item and if it doesn't then you can remove the dragged item from its current cell and append it to its new one.
I have a setInterval function that wont execute if the increment is less than 101ms. The timing will go any number above 100ms. I want to be able to increment the function by 10ms. offY and strtPos also become undefined when the timing goes below 101ms. How do I make it work the same as it is, but instead, have it incremented by 10ms?
var strtPos;
var offY;
var offX;
var hold = true;
var obj = document.getElementById('obj');
var st = function() {
offY = obj.offsetTop;
}
var init = setInterval(function() {
other()
}, 101); //<-- When I change that value to below 101, it prevents the code from working
var other = function() {
if (hold) {
strt();
hold = false
};
console.log(offY)
console.log(strtPos)
if (strtPos - 100 <= offY) {
obj.style.top = (offY - 11) + "px";
} else {
clearInterval(init);
hold = true;
}
}
var strt = function() {
strtPos = offY
}
setInterval(st, 100)
body {
margin: 0;
}
#obj {
width: 50px;
height: 100px;
background-color: red;
position: fixed;
}
<div id="obj"></div>
The short answer is you need to give offY a value that is not undefined initially, so I've rearranged the variables at the top of your code.
Originally, offY only gets a value after ~100ms (setInterval(st, 100)), and without a value that is not undefined the otherfunction's calculations won't work. So you needed the st function to execute first, hence requiring a value > 100.
var strtPos;
var offX;
var hold = true;
var obj = document.getElementById('obj');
var offY = obj.offsetTop;
var st = function() {
offY = obj.offsetTop;
}
var init = setInterval(function() {
other()
}, 10);
var other = function() {
if (hold) {
strt();
hold = false
};
console.log(offY)
console.log(strtPos)
if (strtPos - 100 <= offY) {
obj.style.top = (offY - 11) + "px";
} else {
clearInterval(init);
hold = true;
}
}
var strt = function() {
strtPos = offY
}
setInterval(st, 100)
body {
margin: 0;
}
#obj {
width: 50px;
height: 100px;
background-color: red;
position: fixed;
}
<div id="obj"></div>
Sorry for the long question.
I have tried to create a meetings on a calendar for a day. I need help to take care of the overlapping intervals.
The code I have written in following :
HTML
<body>
<div id="timeline"></div>
<div id="calendar" class="calendar">
</div>
</body>
CSS
.calendar {
border: 1px solid black;
position: absolute;
width: 600px;
height: 1440px;
left: 60px;
}
.event {
position: absolute;
float: left;
width: 100%;
overflow: auto;
border: 0px solid red;
}
#timeline {
position: absolute;
float: left;
}
JS
function getRandomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function creatTimeline(tl) {
var i = 0;
while (i < tl.length) {
var divEl = document.createElement('div');
divEl.style.width = '50px';
divEl.style.height = '120px';
divEl.style.border = '0px solid yellow';
divEl.innerHTML = tl[i];
var timeLine = document.getElementById('timeline');
timeLine.appendChild(divEl);
i++;
}
}
function appendEventDivs(eventArr) {
var i = 0;
while (i < eventArr.length) {
var eventEl = document.createElement('div');
eventEl.className = 'event';
eventEl.style.height = eventArr[i].height;
eventEl.style.top = eventArr[i].top;
eventEl.style.background = eventArr[i].color;
eventEl.style.width = eventArr[i].width;
eventEl.style.left = eventArr[i].left;
eventEl.innerHTML = 'Meeting' + eventArr[i].id;
var cl = document.getElementById('calendar');
cl.appendChild(eventEl);
i++;
}
}
function collidesWith(a, b) {
return a.end > b.start && a.start < b.end;
}
function checkCollision(eventArr) {
for (var i = 0; i < eventArr.length; i++) {
eventArr[i].cols = [];
for (var j = 0; j < eventArr.length; j++) {
if (collidesWith(eventArr[i], eventArr[j])) {
eventArr[i].cols.push(i);
}
}
}
return eventArr;
}
function updateEvents(eventArr) {
eventArr = checkCollision(eventArr);
var arr = [];
arr = eventArr.map(function(el) {
//just to differentiate each event with different colours
el.color = getRandomColor();
el.height = (el.end - el.start) * 2 + 'px';
el.top = (el.start) * 2 + 'px';
el.width = (600 / el.cols.length) + 'px';
return el;
});
return arr;
}
var events = [{
id: 123,
start: 60,
end: 150
}, {
id: 124,
start: 540,
end: 570
}, {
id: 125,
start: 555,
end: 600
}, {
id: 126,
start: 585,
end: 660
}];
var timeline = ['9AM', '10AM', '11AM', '12Noon', '1PM', '2PM', '3PM', '4PM', '5PM', '6PM', '7PM', '8PM', '9PM'];
function getEvents (eventArr) {
eventArr.sort(function(a, b) {
return a.start - b.start;
});
eventArr = updateEvents(eventArr);
appendEventDivs(eventArr);
console.log(eventArr);
//PART 1 - function returning the eventArr with all the required attributes
return eventArr;
};
creatTimeline(timeline);
getEvents(events);
Working fiddle here
Can anybody guide me how to take care of the overlapping intervals so that they appear side-by-side and not on top of each other.
Thanks in advance.
You need to figure out in which column each of the events should be before you can determine their width or left-position. To do this, you need to also store which of the colliding events came before each event:
function checkCollision(eventArr) {
for (var i = 0; i < eventArr.length; i++) {
eventArr[i].cols = [];
eventArr[i].colsBefore=[];
for (var j = 0; j < eventArr.length; j++) {
if (collidesWith(eventArr[i], eventArr[j])) {
eventArr[i].cols.push(j);
if(i>j) eventArr[i].colsBefore.push(j); //also list which of the conflicts came before
}
}
}
return eventArr;
}
Now, we can figure out the column of each event. Once we've done that, we can figure out how wide they should be, and with that, the horizontal positioning should be easy. This should be done inside your updateEvents function. I've got more detailed explanation commented in the comments of the code below.
function updateEvents(eventArr) {
eventArr = checkCollision(eventArr);
var arr=eventArr.slice(0); //clone the array
for(var i=0; i<arr.length; i++){
var el=arr[i];
el.color = getRandomColor();
el.height = (el.end - el.start) * 2 + 'px';
el.top = (el.start) * 2 + 'px';
if(i>0 && el.colsBefore.length>0){ //check column if not the first event and the event has collisions with prior events
if(arr[i-1].column>0){ //if previous event wasn't in the first column, there may be space to the left of it
for(var j=0;j<arr[i-1].column;j++){ //look through all the columns to the left of the previous event
if(el.colsBefore.indexOf(i-(j+2))===-1){ //the current event doesn't collide with the event being checked...
el.column=arr[i-(j+2)].column; //...and can be put in the same column as it
}
}
if(typeof el.column==='undefined') el.column=arr[i-1].column+1; //if there wasn't any free space, but it ito the right of the previous event
}else{
var column=0;
for(var j=0;j<el.colsBefore.length;j++){ //go through each column to see where's space...
if(arr[el.colsBefore[el.colsBefore.length-1-j]].column==column) column++;
}
el.column=column;
}
}else el.column=0;
}
//We need the column for every event before we can determine the appropriate width and left-position, so this is in a different for-loop:
for(var i=0; i<arr.length; i++){
arr[i].totalColumns=0;
if(arr[i].cols.length>1){ //if event collides
var conflictGroup=[]; //store here each column in the current event group
var conflictingColumns=[]; //and here the column of each of the events in the group
addConflictsToGroup(arr[i]);
function addConflictsToGroup(a){
for(k=0;k<a.cols.length;k++){
if(conflictGroup.indexOf(a.cols[k])===-1){ //don't add same event twice to avoid infinite loop
conflictGroup.push(a.cols[k]);
conflictingColumns.push(arr[a.cols[k]].column);
addConflictsToGroup(arr[a.cols[k]]); //check also the events this event conflicts with
}
}
}
arr[i].totalColumns=Math.max.apply(null, conflictingColumns); //set the greatest value as number of columns
}
arr[i].width=(600/(arr[i].totalColumns+1))+'px';
arr[i].left=(600/(arr[i].totalColumns+1)*arr[i].column)+'px';
}
return arr;
}
Working Fiddle: https://jsfiddle.net/ilpo/ftbjan06/5/
I added a few other events to test different scenarios.
Oh, and by the way, absolutely positioned elements can't float.
You already know the top and height of every event, so you could map the calendar and check an event already exist within the area it will occupy, then offset the left value by the number of existing events.
For page loader i have used the plugin. Following is js:
var QueryLoader = {
overlay: "",
loadBar: "",
preloader: "",
items: new Array(),
doneStatus: 0,
doneNow: 0,
selectorPreload: "body",
ieLoadFixTime: 2000,
ieTimeout: "",
init: function() {
if (navigator.userAgent.match(/MSIE (\d+(?:\.\d+)+(?:b\d*)?)/) == "MSIE 6.0,6.0") {
//break if IE6
return false;
}
if (QueryLoader.selectorPreload == "body") {
QueryLoader.spawnLoader();
QueryLoader.getImages(QueryLoader.selectorPreload);
QueryLoader.createPreloading();
} else {
$(document).ready(function() {
QueryLoader.spawnLoader();
QueryLoader.getImages(QueryLoader.selectorPreload);
QueryLoader.createPreloading();
});
}
//help IE drown if it is trying to die :)
QueryLoader.ieTimeout = setTimeout("QueryLoader.ieLoadFix()", QueryLoader.ieLoadFixTime);
},
ieLoadFix: function() {
var ie = navigator.userAgent.match(/MSIE (\d+(?:\.\d+)+(?:b\d*)?)/);
if (ie[0].match("MSIE")) {
while ((100 / QueryLoader.doneStatus) * QueryLoader.doneNow < 100) {
QueryLoader.imgCallback();
}
}
},
imgCallback: function() {
QueryLoader.doneNow ++;
QueryLoader.animateLoader();
},
getImages: function(selector) {
var everything = $(selector).find("*:not(script)").each(function() {
var url = "";
if ($(this).css("background-image") != "none") {
var url = $(this).css("background-image");
} else if (typeof($(this).attr("src")) != "undefined" && $(this).attr("tagName").toLowerCase() == "img") {
var url = $(this).attr("src");
}
url = url.replace("url(\"", "");
url = url.replace("url(", "");
url = url.replace("\")", "");
url = url.replace(")", "");
if (url.length > 0) {
QueryLoader.items.push(url);
}
});
},
createPreloading: function() {
QueryLoader.preloader = $("<div></div>").appendTo(QueryLoader.selectorPreload);
$(QueryLoader.preloader).css({
height: "0px",
width: "0px",
overflow: "hidden"
});
var length = QueryLoader.items.length;
QueryLoader.doneStatus = length;
for (var i = 0; i < length; i++) {
var imgLoad = $("<img></img>");
$(imgLoad).attr("src", QueryLoader.items[i]);
$(imgLoad).unbind("load");
$(imgLoad).bind("load", function() {
QueryLoader.imgCallback();
});
$(imgLoad).appendTo($(QueryLoader.preloader));
}
},
spawnLoader: function() {
if (QueryLoader.selectorPreload == "body") {
var height = $(window).height();
var width = $(window).width();
var position = "fixed";
} else {
var height = $(QueryLoader.selectorPreload).outerHeight();
var width = $(QueryLoader.selectorPreload).outerWidth();
var position = "absolute";
}
var left = $(QueryLoader.selectorPreload).offset()['left'];
var top = $(QueryLoader.selectorPreload).offset()['top'];
QueryLoader.overlay = $("<div></div>").appendTo($(QueryLoader.selectorPreload));
$(QueryLoader.overlay).addClass("QOverlay");
$(QueryLoader.overlay).css({
position: position,
top: top,
left: left,
width: width + "px",
height: height + "px"
});
QueryLoader.loadBar = $("<div></div>").appendTo($(QueryLoader.overlay));
$(QueryLoader.loadBar).addClass("QLoader");
$(QueryLoader.loadBar).css({
position: "relative",
top: "50%",
width: "0%"
});
},
animateLoader: function() {
var perc = (100 / QueryLoader.doneStatus) * QueryLoader.doneNow;
if (perc > 99) {
$(QueryLoader.loadBar).stop().animate({
width: perc + "%"
}, 500, "linear", function() {
QueryLoader.doneLoad();
});
} else {
$(QueryLoader.loadBar).stop().animate({
width: perc + "%"
}, 500, "linear", function() { });
}
},
doneLoad: function() {
//prevent IE from calling the fix
clearTimeout(QueryLoader.ieTimeout);
//determine the height of the preloader for the effect
if (QueryLoader.selectorPreload == "body") {
var height = $(window).height();
} else {
var height = $(QueryLoader.selectorPreload).outerHeight();
}
//The end animation, adjust to your likings
$(QueryLoader.loadBar).animate({
height: height + "px",
top: 0
}, 500, "linear", function() {
$(QueryLoader.overlay).fadeOut(800);
$(QueryLoader.preloader).remove();
});
}
}
In my html file, I used following Javascript:
<script type='text/javascript'>
QueryLoader.init();
</script>
And css is as following:
.QOverlay {
background-color: #000000;
z-index: 9999;
}
.QLoader {
background-color: #CCCCCC;
height: 1px;
}
I used this for simple example it works well.
But when I used this for my site it is giving error in js file as following:
TypeError: $(...).offset(...) is undefined
var left = $(QueryLoader.selectorPreload).offset()['left'];
So please can you help out from this issue:
Thanks in advance..
Your plugin is not really one. Go see the documentation to see more details.
Anyway, even if it's not a plugin, it could work as an object using some jQuery functions.
First, you shouldn't call the object inside the object function.
IE :
QueryLoader.ieTimeout = setTimeout("QueryLoader.ieLoadFix()", QueryLoader.ieLoadFixTime);
Here, you got your first error which you could have seen in the console. It should be :
this.ieTimeout = setTimeout(this.ieLoadFix, this.ieLoadFixTime);
You can start debugging like this, up to your initial error here.
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>