I'm trying to realise an infinite up and down move of the platform . How can I modify the code to get this thing ? I have only managed to have just one un-down movement . I know that I could do this with CSS animations but I would like to modify my code .
var n = 0;
var grid = document.querySelector('.grid');
function move() {
const pixels = [200, 196, 192, 188, 184, 180, 176, 172, 168, 164, 160, 164, 168, 172, 176, 180];
const style = grid.style.bottom
const computedStyle = window.getComputedStyle(grid)
console.log('bottom from computed style', computedStyle.bottom)
grid.style.bottom = pixels[n] + 'px';
n++;
}
move();
setInterval(move, 90);
.grid {
background-color: blue;
height: 20px;
width: 100px;
left: 100px;
bottom: 200px;
position: absolute;
}
<div class="grid"></div>
The function is not looping because you are not resetting n - after the first up down movement, n goes out of bounds, grid.style.bottom = pixels[n] + 'px'; tries to set the stile to undefined +'px' and fails, and the bar stays where it is.
I added n = n % pixels.length; to reset n once it goes out of bounds.
var n = 0;
var grid = document.querySelector('.grid');
function move() {
const pixels = [200, 196, 192, 188, 184, 180, 176, 172, 168, 164, 160, 164, 168, 172, 176, 180];
const style = grid.style.bottom
const computedStyle = window.getComputedStyle(grid)
console.log('bottom from computed style', computedStyle.bottom)
n = n % pixels.length;
grid.style.bottom = pixels[n] + 'px';
n++;
}
move();
setInterval(move, 90);
.grid {
background-color: blue;
height: 20px;
width: 100px;
left: 100px;
bottom: 200px;
position: absolute;
}
<div class="grid"></div>
You can have a Boolean checking once you get at the end of your array and once you do you start to decrement your n variable. This way it will go from 200px -> 180px -> 200px
let grid = document.querySelector(".grid");
let n = 0;
let bool = true
function move() {
const pixels = [200, 196, 192, 188, 184, 180, 176, 172, 168, 164, 160, 164, 168, 172, 176, 180]
const style = grid.style.bottom;
const computedStyle = window.getComputedStyle(grid);
console.log("bottom from computed style", computedStyle.bottom);
grid.style.bottom = pixels[n] + "px";
if(n === pixels.length - 1 ){
bool = false
} else if(n === 0){
bool = true
}
bool ? n++ : n--
}
move();
setInterval(move, 90);
.grid {
background-color: blue;
height: 20px;
width: 100px;
left: 100px;
bottom: 200px;
position: absolute;
}
<div class="grid"></div>
Instead of incrementing in an interval, better compute the position based on the time passed since start.
According to your pixels array and your interval, you move 40px up/down every 900ms. (10 steps of 4px / 90ms)
const start = Date.now()
function move(){
const time = Date.now() - start;
let t = time / 900; // the time passed in terms of up/down strokes
// t = t % 1; // turns this into a sawtooth pattern, just up-strokes
// not what we want, we want every other stroke to be a down-stroke.
t = t&1 ? // every other stroke
(1 - t%1) : // move down
(t%1); // otherwise mode up
// now we have out position as a percentage value 0..1;
// let's compute the pixels.
const pos = 200 - 40*t; // start at 200px and travel a fraction of 40px down.
grid.style.bottom = pos + "px";
// rAF is way smoother than your 90ms interval.
requestAnimationFrame(move);
}
move();
const DURATION = 900;
const grid = document.querySelector('.grid');
const start = 0;
function move() {
let t = (Date.now() - start) / 900;
t = t&1 ? 1-t%1 : t%1; // zig-zag
//t = t%1; // sawtooth; try this instead of the zig-zag and see/understand the difference.
//add some easing; try it.
//t = t*t*(3-2*t);
grid.style.bottom = 200 - 40*t + "px";
requestAnimationFrame(move);
}
move();
.grid {
background-color: blue;
height: 20px;
width: 100px;
left: 100px;
bottom: 200px;
position: absolute;
}
<div class="grid"></div>
Related
I am trying to make a box with a nice looking animated gradient.
I can not use the animate function to change the gradient so I try to do it with JavaScript.
I would use the jQuery.css() function, but I can not address the :before and :after selectors with jQuery.
So what I am trying to do is to change the CSS code in #js-style every 100ms in an infinite loop.
But what happens is that it does update the CSS code, but it only applies it when the loop breaks.
Is there a way to tell the browser to check and apply the code every time it changes during the JavaScript loop?
$(document).ready(function() {
// Settings
var steps_per_color = 100;
var timeout = 100;
var colors = [
[255, 0, 158],
[0, 205, 255],
[42, 0, 255],
[168, 0, 253]
];
var cur_base_colors = [
0,
1,
2,
3
];
var cur_goal_colors = [
1,
2,
3,
0
];
var cur_colors = [
colors[0],
colors[1],
colors[2],
colors[3]
];
var def_colors = [
[255, 0, 158],
[0, 205, 255],
[42, 0, 255],
[168, 0, 253]
];
var step = 0;
var step_size, goal, base, text, cur_color;
while (true) {
cur_base_colors.unshift(cur_base_colors[3]);
cur_base_colors.pop();
cur_goal_colors.unshift(cur_goal_colors[3]);
cur_goal_colors.pop();
cur_colors.forEach(function(color, color_index) {
cur_colors[color_index] = colors[cur_base_colors[color_index]];
});
while (step < steps_per_color) {
cur_colors.forEach(function(color, color_index) {
color.forEach(function(item, index) {
base = colors[cur_base_colors[color_index]][index];
goal = colors[cur_goal_colors[color_index]][index];
step_size = (goal - base) / steps_per_color;
cur_color = item + step_size
cur_colors[color_index][index] = cur_color;
def_colors[color_index][index] = Math.round(cur_color);
});
});
text = ".shadow:before, .shadow:after{background: linear-gradient(135deg";
def_colors.forEach(function(item, index) {
text += ", rgb(" + item[0] + ", " + item[1] + ", " + item[2] + ")";
});
text += ");}";
console.log(text);
$('#js-style').html(text);
sleep(timeout);
step += 1;
}
break;
step = 0;
}
});
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
* {
padding: 0;
box-sizing: border-box;
}
body {
background: #555;
margin: 0;
height: 100vh;
width: 100vw;
padding: 20px;
}
.block {
position: relative;
width: 100%;
height: 100%;
color: #f2f2f2;
display: flex;
flex-direction: column;
justify-content: center;
border-radius: 5px;
}
.shadow:before,
.shadow:after {
content: '';
position: absolute;
top: -2px;
left: -2px;
width: calc(100% + 4px);
height: calc(100% + 4px);
background-size: 100%;
z-index: -1;
border-radius: 5px;
}
.shadow:after {
top: -4px;
left: -4px;
width: calc(100% + 10px);
height: calc(100% + 10px);
filter: blur(4px);
opacity: 1;
}
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<div class="block shadow"></div>
there. I use ChartJS and customise tooltip, but have issue with position first and last tooltip's.
Look:
I suppose that in order to fix the problem, I need to use the https://www.chartjs.org/docs/latest/configuration/tooltip.html#position-modes
but, I cannot understand what the formula should be.
CodePen example - https://codepen.io/anon/pen/JzRooy
<html>
<head>
<title>Line Chart with Custom Tooltips</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.js"></script>
<script>
window.chartColors = {
red: 'rgb(255, 99, 132)',
orange: 'rgb(255, 159, 64)',
yellow: 'rgb(255, 205, 86)',
green: 'rgb(75, 192, 192)',
blue: 'rgb(54, 162, 235)',
purple: 'rgb(153, 102, 255)',
grey: 'rgb(231,233,237)'
};
window.randomScalingFactor = function() {
return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
}
</script>
<style>
canvas{
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
#chartjs-tooltip {
opacity: 1;
position: absolute;
background: rgba(0, 0, 0, .7);
color: white;
border-radius: 3px;
-webkit-transition: all .1s ease;
transition: all .1s ease;
pointer-events: none;
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
.chartjs-tooltip-key {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 10px;
}
</style>
</head>
<body>
<canvas id="chart"/>
<script>
Chart.defaults.global.pointHitDetectionRadius = 1;
var customTooltips = function(tooltip) {
var tooltipEl = document.getElementById('chartjs-tooltip');
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'chartjs-tooltip';
tooltipEl.innerHTML = "<div class='wrapper'></div>"
document.body.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltip.yAlign) {
tooltipEl.classList.add(tooltip.yAlign);
} else {
tooltipEl.classList.add('no-transform');
}
function getBody(bodyItem) {
return bodyItem.lines;
}
// Set Text
if (tooltip.body) {
var titleLines = tooltip.title || [];
var bodyLines = tooltip.body.map(getBody);
var innerHtml = '';
titleLines.forEach(function(title) {
innerHtml += '<span style="margin-bottom: 10px;display: inline-block;">' + title + '</span>';
});
innerHtml += '<div style="display: flex;flex-direction: row;">';
bodyLines.forEach(function(body, i) {
var parts = body[0].split(':');
innerHtml += '<div style="display: flex;flex-direction: column;margin-right: 10px;font-size: 12px;">';
innerHtml += '<span>' + parts[0].trim() + '</span>';
innerHtml += '<b>' + parts[1].trim() + '</b>';
innerHtml += '</div>';
});
innerHtml += '</div>';
var root = tooltipEl.querySelector('.wrapper');
root.innerHTML = innerHtml;
}
var canvas = this._chart.canvas;
tooltipEl.style.opacity = 1;
tooltipEl.style.left = canvas.offsetLeft + tooltip.caretX + 'px';
tooltipEl.style.top = canvas.offsetTop + tooltip.caretY + 'px';
tooltipEl.style.fontFamily = tooltip._fontFamily;
tooltipEl.style.fontSize = tooltip.fontSize;
tooltipEl.style.fontStyle = tooltip._fontStyle;
tooltipEl.style.padding = "10px";
tooltipEl.style.border = "1px solid #B4B6C1";
tooltipEl.style.backgroundColor = "#FFFFFF";
tooltipEl.style.color = "#4C4F59";
tooltipEl.style.fontFamily = '"open sans", "helvetica neue", "arial", "sans-serif"';
};
var lineChartData = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
label: "My First dataset",
borderColor: window.chartColors.red,
pointBackgroundColor: window.chartColors.red,
fill: false,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
]
}, {
label: "My Second dataset",
borderColor: window.chartColors.blue,
pointBackgroundColor: window.chartColors.blue,
fill: false,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
]
}]
};
window.onload = function() {
var chartEl = document.getElementById("chart");
window.myLine = new Chart(chartEl, {
type: 'line',
data: lineChartData,
options: {
title:{
display:true,
text:'Chart.js Line Chart - Custom Tooltips'
},
tooltips: {
enabled: false,
mode: 'nearest',
position: 'average',
intersect: false,
custom: customTooltips
}
}
});
};
</script>
</body>
</html>
New modes can be defined by adding functions to the Chart.Tooltip.positioners map (DOC). This function returns the x and y position for the tooltip.
You can add a custom one to adjust the x at an offset.
One way to do this would be to be:
//register custome positioner
Chart.Tooltip.positioners.custom = function(elements, position) {
if (!elements.length) {
return false;
}
var offset = 0;
//adjust the offset left or right depending on the event position
if (elements[0]._chart.width / 2 > position.x) {
offset = 20;
} else {
offset = -20;
}
return {
x: position.x + offset,
y: position.y
}
}
Fiddle example that I created
I hope it helps.
I had the same issue and I didn't find a good solution, so I had to dot it myself.
Actually, it's simple than I thought, wish it helps someone.
Demo: https://codepen.io/themustafaomar/pen/wvWZrod
const labels = ["1 April","2 April","3 April","4 April","5 April","6 April","7 April","8 April","9 April","10 April","11 April","12 April","13 April","14 April","15 April","16 April","17 April","18 April","19 April","20 April","21 April","22 April","23 April","24 April","25 April","26 April","27 April","28 April","29 April","30 April","31 April"]
const data = [ 95, 57, 72, 54, 73, 53, 98, 75, 52, 93, 50, 65, 99, 67, 77, 61, 74, 65, 86, 92, 64, 89, 82, 62, 64, 89, 59, 75, 56, 63 ];
function customTooltips(tooltipModel) {
// Tooltip Element
var tooltipEl = document.getElementById("chartjs-tooltip");
const yAlign = tooltipModel.yAlign;
const xAlign = tooltipModel.xAlign;
// Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement("div");
tooltipEl.id = "chartjs-tooltip";
tooltipEl.innerHTML = "<table></table>";
document.body.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltipModel.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove("top", "bottom", "center", "left", "right");
if (tooltipModel.yAlign || tooltipModel.xAlign) {
tooltipEl.classList.add(tooltipModel.yAlign);
tooltipEl.classList.add(tooltipModel.xAlign);
}
// Set Text
if (tooltipModel.body) {
var titleLines = tooltipModel.title || [];
var bodyLines = tooltipModel.body.map((bodyItem) => {
return bodyItem.lines;
});
var innerHtml = "<thead>";
titleLines.forEach(function (title) {
innerHtml += '<tr><th><div class="mb-1">' + title + "</div></th></tr>";
});
innerHtml += "</thead><tbody>";
bodyLines.forEach((body, i) => {
var colors = tooltipModel.labelColors[i];
// var style = 'background-color:' + colors.borderColor
var style =
"background-color:" + this._chart.data.datasets[i].borderColor;
var value = tooltipModel.dataPoints[i].value;
var label = this._chart.data.datasets[i].label;
style += "; border-color:" + colors.borderColor;
style += "; border-color:" + this._chart.data.datasets[i].borderColor;
style += "; border-width: 2px";
var span =
'<span class="chartjs-tooltip-key" style="' + style + '"></span>';
innerHtml += `<tr><td> ${span} $${value}K </td></tr>`;
});
innerHtml += "</tbody>";
var tableRoot = tooltipEl.querySelector("table");
tableRoot.innerHTML = innerHtml;
}
// Tooltip height and width
const { height, width } = tooltipEl.getBoundingClientRect();
// Chart canvas positions
const positionY = this._chart.canvas.offsetTop;
const positionX = this._chart.canvas.offsetLeft;
// Carets
const caretY = tooltipModel.caretY;
const caretX = tooltipModel.caretX;
// Final coordinates
let top = positionY + caretY - height;
let left = positionX + caretX - width / 2;
let space = 8; // This for making space between the caret and the element.
// yAlign could be: `top`, `bottom`, `center`
if (yAlign === "top") {
top += height + space;
} else if (yAlign === "center") {
top += height / 2;
} else if (yAlign === "bottom") {
top -= space;
}
// xAlign could be: `left`, `center`, `right`
if (xAlign === "left") {
left = left + width / 2 - tooltipModel.xPadding - space / 2;
if (yAlign === "center") {
left = left + space * 2;
}
} else if (xAlign === "right") {
left -= width / 2;
if (yAlign === "center") {
left = left - space;
} else {
left += space;
}
}
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
// Left and right
tooltipEl.style.top = `${top}px`;
tooltipEl.style.left = `${left}px`;
// Font
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + "px";
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
// Paddings
tooltipEl.style.padding =
tooltipModel.yPadding + "px " + tooltipModel.xPadding + "px";
}
// Chart
new Chart("chart", {
type: "line",
data: {
labels,
datasets: [
{
label: "Custom tooltip demo",
borderColor: "#f66",
backgroundColor: "transparent",
lineTension: 0,
borderWidth: 1.5,
pointRadius: 2,
data
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
legend: { display: false },
scales: {
// YAxes
yAxes: [{ display: false }],
// XAxes
xAxes: [
{
display: false,
gridLines: { display: false },
ticks: {
padding: 20,
autoSkipPadding: 30,
maxRotation: 0
}
}
]
},
tooltips: {
enabled: false,
intersect: false,
mode: "index",
position: "average",
custom: customTooltips
}
}
});
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.chartjs-wrapper {
height: 90px;
width: 300px;
margin: 25px auto 0;
border: 1px solid #e6e6e6;
}
#chartjs-tooltip {
opacity: 1;
position: absolute;
color: #fff;
background-color: #000;
border-radius: 6px;
transition: all 0.25s ease-in-out;
pointer-events: none;
}
#chartjs-tooltip:after {
content: "";
display: block;
position: absolute;
margin: auto;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 6px;
}
/* Top center */
#chartjs-tooltip.top.center:after {
border-bottom-color: #000;
top: -11px;
left: 0;
right: 0;
}
/* Top left */
#chartjs-tooltip.top.left:after {
border-bottom-color: #000;
left: 5px;
top: -11px;
}
/* Top right */
#chartjs-tooltip.top.right:after {
border-bottom-color: #000;
right: 5px;
top: -11px;
}
/* Bottom center */
#chartjs-tooltip.bottom.center:after {
border-top-color: #000;
bottom: -11px;
right: 0;
left: 0;
}
/* Bottom left */
#chartjs-tooltip.bottom.left:after {
border-top-color: #000;
bottom: -11px;
}
/* Bottom right */
#chartjs-tooltip.bottom.right:after {
border-top-color: #000;
bottom: -11px;
right: 5px;
}
/* Center left */
#chartjs-tooltip.center.left:after {
border-right-color: #000;
margin: auto;
left: -11px;
bottom: 0;
top: 0;
}
/* Center right */
#chartjs-tooltip.center.right:after {
border-left-color: #000;
margin: auto;
right: -11px;
bottom: 0;
top: 0;
}
.chartjs-tooltip-key {
display: inline-block;
border-radius: 50%;
width: 10px;
height: 10px;
margin-right: 7px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<div class="chartjs-wrapper">
<canvas id="chart"></canvas>
</div>
I did this:
Subtract the pixels by way of centering it or putting it in the position.
tooltipEl.style.left = canvas.offsetLeft + tooltip.caretX - 55 + 'px';
U use this in addition to externalTooltipHandler from chartJs
This will modify position only for latest tooltip
var currentTooltip = tooltip.dataPoints[0].dataIndex;
var keys = Object.keys(tooltip.dataPoints[0].dataset.data);
var latestTooltip = keys[keys.length - 1];
if (currentTooltip == latestTooltip) {
tooltipEl.style.left = chart.canvas.offsetLeft + tooltip.caretX - 70 + "px";
} else {
tooltipEl.style.left = positionX + tooltip.caretX + "px";
}
I'm trying to get various progress bars for my website that load at equal speeds but of different lengths. I have tried this code but it isn't working
var prog = document.getElementsByClassName("prog");
var c = 0;
var ski = [50, 40, 35, 25, 32, 33, 20, 25];
var j;
var t = 0;
function inc() {
if (t == ski[c]) {
clearTimeout(ty);
t = 0;
} else {
t++;
prog[c].style.width = t + "%";
}
}
function ani() {
for (j = 0; j < ski.length; j++) {
var ty = setTimeout(inc, 30);
c = j;
}
}
ani();
The ski array is the length of percent for div to load after animation is complete considering 50% width as a full length of a progress bar. prog is an array of divs of small height and long width.
Please Help.
It looks like you're setting the width of each item to t which is an index in your case. You could try to set it to ski[t].
Basically you're setting each one to 1%, then 2%, etc.
You seem to be massively over-complicating the problem. You can simply loop through each of the .prog elements in the prog array, then set their width. If you want the change to be animated, set transition: width in the CSS of those elements, like this:
window.addEventListener('load', function() {
var prog = document.getElementsByClassName("prog");
var ski = [50, 40, 35, 25, 32, 33, 20, 25];
function ani() {
for (i = 0; i < prog.length; i++) {
prog[i].style.width = (ski[i] * 2) + '%';
}
}
ani();
});
.prog {
width: 0;
height: 25px;
background-color: #C00;
margin: 0 0 5px;
transition: width 0.5s;
}
<div class="prog"></div>
<div class="prog"></div>
<div class="prog"></div>
<div class="prog"></div>
<div class="prog"></div>
<div class="prog"></div>
<div class="prog"></div>
<div class="prog"></div>
I'm using fabricjs (1.7.20) and would like to create a sort of "bleed area" where some space around the canvas isn't usable by the user; a sort of "wall" if you will, to stop objects from being moved to the sides of the canvas walls. How might I accomplish this?
var canvas = new fabric.Canvas("c");
canvas.setHeight(350);
canvas.setWidth(350);
canvas.add(new fabric.IText("Some text", {
top: 25,
}));
var circle = new fabric.Circle({
radius: 20, fill: 'green', left: 100, top: 100
});
var triangle = new fabric.Triangle({
width: 20, height: 30, fill: 'blue', left: 150, top: 150
});
canvas.add(circle, triangle);
canvas {
border: 1px solid #dddddd;
margin-top: 10px;
border-radius: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<canvas id="c"></canvas>
This was suggested to me and it is the closest I've gotten. I'm looking to do this, but 10px, give or take, from the border of the canvas.
canvas.on('object:moving', function (e) {
var obj = e.target;
// if object is too big ignore
if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
return;
}
obj.setCoords();
// top-left corner
if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
}
// bot-right corner
if(obj.getBoundingRect().top+obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width > obj.canvas.width){
obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
}
});
You can add a 10 pixel bleed area by adding / subtracting the value 10 from the conditions that determine if an object is being moved out of the “bleed” area (I'd actually prefer to call it “padding”), and adding / subtracting from the calculation that repositions the object inside the padding boundaries.
Here's an updated example that works:
var padding = 10;
var canvas = new fabric.Canvas("c");
canvas.setHeight(350);
canvas.setWidth(350);
canvas.add(new fabric.IText("Some text", {
top: 25,
}));
var circle = new fabric.Circle({
radius: 20,
fill: 'green',
left: 100,
top: 100
});
var triangle = new fabric.Triangle({
width: 20,
height: 30,
fill: 'blue',
left: 150,
top: 150
});
canvas.add(circle, triangle);
canvas.on('object:moving', function(e) {
var obj = e.target;
// if object is too big ignore
if (obj.currentHeight > obj.canvas.height - padding * 2 ||
obj.currentWidth > obj.canvas.width - padding * 2) {
return;
}
obj.setCoords();
// top-left corner
if (obj.getBoundingRect().top < padding ||
obj.getBoundingRect().left < padding) {
obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top + padding);
obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left + padding);
}
// bot-right corner
if (obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height - padding ||
obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width - padding) {
obj.top = Math.min(
obj.top,
obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top - padding);
obj.left = Math.min(
obj.left,
obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left - padding);
}
});
canvas {
border: 1px solid #dddddd;
margin-top: 10px;
border-radius: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<canvas id="c"></canvas>
In the first line, I've defined a variable “padding” that refers to the desired padding size. This way, if you want to change the padding later on, you don't have to change it in eight different places.
The padding variable used instead of 0 in the condition for the top left corner.
If the “top left” condition is true, the padding is added to the calculation for repositioning the object.
In the “bottom right” part, we are doing the opposite – we subtract the padding from the condition and subtract from the repositioning calculation.
You can also try the code in this fiddle: https://jsfiddle.net/pahund/hz7jLnme/
Good day people!
I have run into an issue with my simple single day calendar script.
I've been tasked to create a single day calendar, which shows each block hour from 9am to 6pm. If an event overlaps another, they should equal the same width and not overlap. I have managed to achieve this for two events, however if more than two overlap, things go abit south, I need help figuring out a method to fix this where any number of events overlap, their widths will equal the same.
Events are rendered on the calendar using a global function:
renderDay([{start: 30, end: 120},{start: 60, end: 120}])
which takes an array of objects as an argument, where the integers are the number of minutes pasted from 9am. eg. 30 is 9:30am, 120 is 11am
here is the collision function I took from stackoverflow
// collision function to return boolean
// attribute: http://stackoverflow.com/questions/14012766/detecting-whether-two-divs-overlap
function collision($div1, $div2) {
let x1 = $div1.offset().left;
let y1 = $div1.offset().top;
let h1 = $div1.outerHeight(true);
let w1 = $div1.outerWidth(true);
let b1 = y1 + h1;
let r1 = x1 + w1;
let x2 = $div2.offset().left;
let y2 = $div2.offset().top;
let h2 = $div2.outerHeight(true);
let w2 = $div2.outerWidth(true);
let b2 = y2 + h2;
let r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
return true;
}
I run a loop on all the event divs which I want to check for overlaps
// JQuery on each, check if collision
$('.event').each(function(index, value) {
// statement to break out on final loop
if(index === $('.event').length - 1) return;
console.log('at index: ', index);
// if collison === true, halve width of both event divs, re-position
if(collision( $('#e-'+index) , $('#e-'+(index + 1)) )) {
$('#e-'+index).css('width', $('#e-'+index).width() / 2);
$('#e-'+(index+ 1)).css('width', $('#e-'+(index+ 1)).width() / 2).css('left', $('#e-'+(index + 1)).width());
if(collision)
}
})
}
})
Screenshots to help visualize :)
When two overlap, they have equal widths
When three or more overlap, things go wrong
Any help would be greatly appreciated!
DW
After looking at the code, it seems overly complex to check the rendered elements for collision when you can work this out from the start and end times.
The way I've done it, is to group events which collide in arrays like so:
let collisions = [
// only 1 event in this array so no collisions
[{
start: 30,
end: 120
}],
// 3 events in this array which have overlapping times
[{
start: 300,
end: 330
}, {
start: 290,
end: 330
}, {
start: 300,
end: 330
}]
];
Then we iterate through each group of collisions, and create the elements with the appropriate width and positioning.
for (var i = 0; i < collisions.length; i++) {
var collision = collisions[i];
for (var j = 0; j < collision.length; j++) {
var event = collision[j];
let height = event.end - event.start;
let top = event.start + 50;
// 360 = max width of event
let width = 360 / collision.length;
// a lot of this could be moved into a css class
// I removed the "display: inline-block" code because these are absolutely positioned. Replaced it with "left: (j * width)px"
let div = $(`<div id=${'e-'+ (i + j)}>`).css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('left', (j * width) + 'px')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
// append event div to parent container
$('#events').append(div);
}
}
//**********************************************************************
//
// TITLE - Thought Machine Coding Challenge, Single Day Calendar
// AUTHOR - DOUGLAS WISSETT WALKER
// DATE - 21/04/2016
// VERSION - 0.0.3
// PREVIOUS - 0.0.2
//
//**********************************************************************
let arr = [{
start: 30,
end: 120
}, {
start: 70,
end: 180
}, {
start: 80,
end: 190
}, {
start: 300,
end: 330
}, {
start: 290,
end: 330
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 400,
end: 440
}, {
start: 20,
end: 200
}];
let renderDay;
$(document).ready(() => {
renderDay = function(array) {
$('.event').each(function(i, el) {
$(el).remove();
});
// background colors for events
let arrayOfColors = [
'rgba(255, 153, 153, 0.75)',
'rgba(255, 204, 153, 0.75)',
'rgba(204, 255, 153, 0.75)',
'rgba(153, 255, 255, 0.75)',
'rgba(153, 153, 255, 0.75)',
'rgba(255, 153, 255, 0.75)'
]
let collisions = mapCollisions(array);
let eventCount = 0; // used for unique id
for (let i = 0; i < collisions.length; i++) {
let collision = collisions[i];
for (let j = 0; j < collision.length; j++) {
let event = collision[j];
let height = event.end - event.start;
let top = event.start + 50;
// 360 = max width of event
let width = 360 / collision.length;
// a lot of this could be moved into a css class
// I removed the "display: inline-block" code because these are absolutely positioned
// Replaced it with "left: (j * width)px"
let div = $("<div id='e-" + eventCount + "'>").css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('left', (j * width) + 'px')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
eventCount++;
// append event div to parent container
$('#events').append(div);
}
}
}
renderDay(arr);
});
// Sorry this is pretty messy and I'm not familiar with ES6/Typescript or whatever you are using
function mapCollisions(array) {
let collisions = [];
for (let i = 0; i < array.length; i++) {
let event = array[i];
let collides = false;
// for each group of colliding events, check if this event collides
for (let j = 0; j < collisions.length; j++) {
let collision = collisions[j];
// for each event in a group of colliding events
for (let k = 0; k < collision.length; k++) {
let collidingEvent = collision[k]; // event which possibly collides
// Not 100% sure if this will catch all collisions
if (
event.start >= collidingEvent.start && event.start < collidingEvent.end || event.end <= collidingEvent.end && event.end > collidingEvent.start || collidingEvent.start >= event.start && collidingEvent.start < event.end || collidingEvent.end <= event.end && collidingEvent.end > event.start) {
collision.push(event);
collides = true;
break;
}
}
}
if (!collides) {
collisions.push([event]);
}
}
console.log(collisions);
return collisions;
}
html,
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
#container {
height: 100%;
width: 100%;
}
#header-title {
text-align: center;
}
#calendar {
width: 400px;
height: 620px;
margin-top: 70px;
}
#events {
position: absolute;
top: 80px;
left: 100px;
width: 800px;
height: 620px;
}
.event {
box-shadow: 0 0 20px black;
border-radius: 5px;
}
.hr-block {
border-top: 2px solid black;
height: 58px;
margin: 0;
padding: 0;
margin-left: 100px;
min-width: 360px;
opacity: .5;
}
.hr-header {
position: relative;
top: -33px;
left: -68px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/responsive.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script charset="UTF-8" src="js/moment.js"></script>
<script charset="UTF-8" src="js/script2.js"></script>
<title>Thought Machine Code Challenge</title>
</head>
<body>
<div id="container">
<div class="header">
<h1 id="header-title"></h1>
</div>
<div id="calendar">
<div class="hr-block">
<h2 class="hr-header">09:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">10:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">11:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">12:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">13:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">14:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">15:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">16:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">17:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">18:00</h2>
</div>
</div>
</div>
<div id="events">
</div>
<script>
document.getElementById("header-title").innerHTML = moment().calendar();
</script>
</body>
</html>
working index, script and css files
//**********************************************************************
//
// TITLE - Thought Machine Coding Challenge, Single Day Calendar
// AUTHOR - DOUGLAS WISSETT WALKER
// DATE - 21/04/2016
// VERSION - 0.0.3
// PREVIOUS - 0.0.2
//
//**********************************************************************
let arr = [{start: 30, end: 120},{start: 300, end: 330},{start: 290, end: 330}];
let renderDay;
$(document).ready(() => {
renderDay = function(array) {
$('.event').each(function(i, el) {
$(el).remove();
});
// background colors for events
let arrayOfColors = [
'rgba(255, 153, 153, 0.75)',
'rgba(255, 204, 153, 0.75)',
'rgba(204, 255, 153, 0.75)',
'rgba(153, 255, 255, 0.75)',
'rgba(153, 153, 255, 0.75)',
'rgba(255, 153, 255, 0.75)'
]
// iterate through each event time
array.forEach((eventTimes, index) => {
// define event height and top position on calendar
let height = eventTimes.end - eventTimes.start;
let top = eventTimes.start + 50;
// max width of event
let width = 360;
// create event div
let div = $(`<div id=${'e-'+index}>`).css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('display', 'inline-block')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
// append event div to parent container
$('#events').append(div);
})
// JQuery on each, check if collision
$('.event').each(function(index, value) {
// statement to break out on final loop
if(index === $('.event').length - 1) return;
console.log('at index: ', index);
// if collison === true, halve width of both event divs, re-position
if(collision( $('#e-'+index) , $('#e-'+(index + 1)) )) {
$('#e-'+index).css('width', $('#e-'+index).width() / 2);
$('#e-'+(index+ 1)).css('width', $('#e-'+(index+ 1)).width() / 2).css('left', $('#e-'+(index + 1)).width());
}
})
}
})
// collision function to return boolean
// attribute: http://stackoverflow.com/questions/14012766/detecting-whether-two-divs-overlap
function collision($div1, $div2) {
let x1 = $div1.offset().left;
let y1 = $div1.offset().top;
let h1 = $div1.outerHeight(true);
let w1 = $div1.outerWidth(true);
let b1 = y1 + h1;
let r1 = x1 + w1;
let x2 = $div2.offset().left;
let y2 = $div2.offset().top;
let h2 = $div2.outerHeight(true);
let w2 = $div2.outerWidth(true);
let b2 = y2 + h2;
let r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
return true;
}
// render events using renderDay(arr) in console
html, body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
#container {
height: 100%;
width: 100%;
}
#header-title {
text-align: center;
}
#calendar {
width: 400px;
height: 620px;
margin-top: 70px;
}
#events {
position: absolute;
top: 80px;
left: 100px;
width: 800px;
height: 620px;
}
.event {
box-shadow: 0 0 20px black;
border-radius: 5px;
}
.hr-block {
border-top: 2px solid black;
height: 58px;
margin: 0;
padding: 0;
margin-left: 100px;
min-width: 360px;
opacity: .5;
}
.hr-header {
position: relative;
top: -33px;
left: -68px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/responsive.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script charset="UTF-8" src="js/moment.js"></script>
<script charset="UTF-8" src="js/script2.js"></script>
<title>Thought Machine Code Challenge</title>
</head>
<body>
<div id="container">
<div class="header">
<h1 id="header-title"></h1>
</div>
<div id="calendar">
<div class="hr-block"><h2 class="hr-header">09:00</h2></div>
<div class="hr-block"><h2 class="hr-header">10:00</h2></div>
<div class="hr-block"><h2 class="hr-header">11:00</h2></div>
<div class="hr-block"><h2 class="hr-header">12:00</h2></div>
<div class="hr-block"><h2 class="hr-header">13:00</h2></div>
<div class="hr-block"><h2 class="hr-header">14:00</h2></div>
<div class="hr-block"><h2 class="hr-header">15:00</h2></div>
<div class="hr-block"><h2 class="hr-header">16:00</h2></div>
<div class="hr-block"><h2 class="hr-header">17:00</h2></div>
<div class="hr-block"><h2 class="hr-header">18:00</h2></div>
</div>
</div>
<div id="events">
</div>
<script>
document.getElementById("header-title").innerHTML = moment().calendar();
</script>
</body>
</html>
events not rendering, you need to run:
renderDay([{start:30, end:120, start: 60, end: 120}]) in console