Delay in jquery animate with if condition - javascript

I'm trying to make a simple slider with 3 images and 3 bullets. My problem is that when I use jquery .animate to go to the next bullet, the first time it works correctly but the second time shows me in the first position with delay, which makes jquery .animate be delayed in other bullets.
My code in JSFiddle https://jsfiddle.net/Lqpj7sx1/
My code in StackOverflow snippet:
$(document).ready(function(){
//Encuentro la cantidad de imagenes
var cantImagenes = $('#imagenes img').length;
//Genero los bullets de acuerdo a la cantidad de imagenes
for(var i = 0; i < cantImagenes; i++)
{
$("#bullets").append("<span class='bullet' id=" + i + "></span>");
$('#bullets #0').addClass("seleccion");
var cantBullet = $("span.bullet").length;
}
var currentBullet = 0;
function animacion(){
$("#texto3").slideDown(4000, function(){
$("#texto3").css("display", "none")
$("#texto2").slideDown(4000, function(){
$("#texto2").css("display", "none")
$("#texto1").slideDown(4000, function(){
$("#texto1").css("display", "none")
})})});
for(i = 0; i < cantBullet; i++)
{
$("#bullets").animate({"left": "+=600px"}, 4000, function(){
$("#bullets #"+currentBullet).removeClass("seleccion");
currentBullet = currentBullet + 1;
$("#bullets #"+currentBullet).addClass("seleccion");
if(currentBullet > cantBullet){
currentBullet = 0;
$("#bullets #"+currentBullet).addClass("seleccion");
}
});
}
$("#foto3").animate({"left": "+=600px"}, 4000, function(){
$("#foto2").animate({"left": "+=600px"}, 4000, function(){
$("#foto1").animate({"left": "+=600px"}, 4000, function(){
$("#foto3").css("left", "0")
$("#foto2").css("left", "0")
$("#foto1").css("left", "0")
animacion();
})})});
}
animacion();
});
#imagenes{
width: 600px;
height: 450px;
border: 1px solid grey;
position: relative;
overflow: hidden;
background-image: url(http://1u88jj3r4db2x4txp44yqfj1.wpengine.netdna-cdn.com/wp-content/uploads/2014/05/big-data-600x450.jpg)
}
#foto1, #foto2, #foto3{
display: block;
position: absolute;
}
#texto1, #texto2, #texto3{
display: none;
position: absolute;
text-align: center;
width: 600px;
height: 30px;
padding-top: 10px;
vertical-align: bottom;
background-color: #000000;
color: #FFFFFF;
opacity: 0.3;
filter: alpha(opacity=30);
}
.bullet
{
width: 20px;
height: 20px;
border:3px solid #000;
margin-right: 10px;
display: inline-block;
}
.seleccion
{
background: #ccc;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<h2>Slider</h2>
<div id="imagenes">
<img src="https://www.koi-nya.net/img/subidos_posts/2016/08/PS4-Slim_08-21-16_006-600x450.jpg" id="foto1" />
<img src="https://www.euroresidentes.com/estilo-de-vida/moda-estilo/wp-content/uploads/sites/15/2014/09/collage-leggings.jpg" id="foto2" />
<img src="http://1u88jj3r4db2x4txp44yqfj1.wpengine.netdna-cdn.com/wp-content/uploads/2014/05/big-data-600x450.jpg" id="foto3" />
<div id="texto1">Picture 1</div>
<div id="texto2">Picture 2</div>
<div id="texto3">Picture 3</div>
</div>
<div id="bullets"></div>
My problem is specifically in these lines:
for(i = 0; i < cantBullet; i++)
{
$("#bullets").animate({"left": "+=600px"}, 4000, function(){
$("#bullets #"+currentBullet).removeClass("seleccion");
currentBullet = currentBullet + 1;
$("#bullets #"+currentBullet).addClass("seleccion");
if(currentBullet > cantBullet){
currentBullet = 0;
$("#bullets #"+currentBullet).addClass("seleccion");
}
});
}
I hope someone can help me! Thank you!

Check this [https://jsfiddle.net/8hoo7tc9/]
Actually for loop was not properly set
[1]: https://jsfiddle.net/8hoo7tc9/

Related

How do I stop a function? ( jQuery, .hover() )

When you hover over the squares of the picture change one after another using the interval function. Can't solve 2 problems.
1. The function should be started only at the square on which the point.
2. The function should stop if the cursor has left the square.
Help me think.
$('.block').hover(function(){
setInterval(myFuncSuper2, 3000);
});
// Change pic on hover
function changePic(i) {
setTimeout(function () {
jQuery(".hero-cat_" + i).addClass("active");
jQuery(".hero-cat_" + i).siblings().removeClass("active")
}, i * 1000)
}
function myFuncSuper2() {
for (let i = 0; i <= 3; i++) {
changePic(i);
}
}
.block {
width: 100px;
height: 100px;
border: 1px solid;
position: relative;
cursor: pointer;
}
.block img {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
display: none;
}
.block img.active {
display: inline;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<div class="block">
<img src="https://avatars.mds.yandex.net/get-pdb/38069/39782144-fdc6-473f-b757-b8209a2e4b31/s1200" class="hero-cat_1">
<img src="https://avatars.mds.yandex.net/get-pdb/225396/aea85590-65df-49b9-b959-d14cefbd9d38/s1200" class="hero-cat_2">
<img src="https://avatars.mds.yandex.net/get-pdb/34158/e223aed5-4ea8-4e85-bb69-88e207b6c16b/s1200" class="hero-cat_3">
</div>
<div class="block">
<img src="https://avatars.mds.yandex.net/get-pdb/38069/39782144-fdc6-473f-b757-b8209a2e4b31/s1200" class="hero-cat_1">
<img src="https://avatars.mds.yandex.net/get-pdb/225396/aea85590-65df-49b9-b959-d14cefbd9d38/s1200" class="hero-cat_2">
<img src="https://avatars.mds.yandex.net/get-pdb/34158/e223aed5-4ea8-4e85-bb69-88e207b6c16b/s1200" class="hero-cat_3">
</div>
What you are trying to achieve needs two things:
mouseenter and mouseleave
1.2. because mousehover will keep on adding events when you keep hovering. you need to stop that.
And this for context of current element
2.1 because you need to enable only selected element for hover, not entire .block set
$('.block').mouseenter(startMyFunc);
$('.block').mouseleave(stopMyFunc);
var myInterval;
function myFuncSuper2() {
for (let i = 0; i <= 3; i++) {
setTimeout(function() {
jQuery(this).find(".hero-cat_" + i).addClass("active");
jQuery(this).find(".hero-cat_" + i).siblings().removeClass("active")
}.bind(this), i * 300)
}
}
function startMyFunc() {
myInterval = window.setInterval(myFuncSuper2.bind(this), 1000)
}
function stopMyFunc() {
if (myInterval) {
clearInterval(myInterval);
}
}
.block {
display: inline-block;
width: 100px;
height: 100px;
border: 1px solid;
position: relative;
cursor: pointer;
}
.block img {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
display: none;
}
.block img.active {
display: inline;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<div class="block">
<img src="https://avatars.mds.yandex.net/get-pdb/38069/39782144-fdc6-473f-b757-b8209a2e4b31/s1200" class="hero-cat_1">
<img src="https://avatars.mds.yandex.net/get-pdb/225396/aea85590-65df-49b9-b959-d14cefbd9d38/s1200" class="hero-cat_2">
<img src="https://avatars.mds.yandex.net/get-pdb/34158/e223aed5-4ea8-4e85-bb69-88e207b6c16b/s1200" class="hero-cat_3">
</div>
<div class="block">
<img src="https://avatars.mds.yandex.net/get-pdb/38069/39782144-fdc6-473f-b757-b8209a2e4b31/s1200" class="hero-cat_1">
<img src="https://avatars.mds.yandex.net/get-pdb/225396/aea85590-65df-49b9-b959-d14cefbd9d38/s1200" class="hero-cat_2">
<img src="https://avatars.mds.yandex.net/get-pdb/34158/e223aed5-4ea8-4e85-bb69-88e207b6c16b/s1200" class="hero-cat_3">
</div>
-- Edit: added clearing the timeouts created in myFuncSuper2 ---
Set the function for hover off, and clear the interval
var interval = null;
var timeouts = [0,0,0];
$('.hero-category').hover(function () {
interval = setInterval(myFuncSuper2, 3000)
}, function() {
if (interval) clearInterval(interval);
for (var i = 0; i < timeouts.length; i++) {
if (timeouts[i] !== 0) {
clearTimeout(timeouts[i]);
}
}
});
function myFuncSuper2() {
for (let i = 0; i <= 3; i++) {
timeouts[i] = setTimeout(function() {
jQuery(this).find(".hero-cat_" + i).addClass("active");
jQuery(this).find(".hero-cat_" + i).siblings().removeClass("active")
}.bind(this), i * 300)
}
}

JQuery on click Progress Bar

I'm trying to get the progress bar animation to run when I click the .trigger. I'm getting the data-percentage value in the logs but the animation isn't running. It works without using $(this).closest() but I cannot figure out why the animation isn't running with my current JS code.
$(".list").on("click", ".trigger", function() {
var e = $(this).closest(".item");
$(".progressbar").attr("data-percentage", e.find("#percent").text());
var t = e.find("#percent").text();
return (
$(".progressbar").each(function() {
var n = e,
r = t;
console.log(t),
parseInt(n.data("percentage"), 10) < 2 && (r = 2),
n.children(".bar").animate({
width: r + "%"
}, 500);
}), !1
);
});
.item {
width: 50px;
height: 50px;
border: 1px solid;
}
.trigger {
height: 30px;
width: 30px;
border: 3px solid blue;
cursor: pointer;
}
.progressbar {
position: relative;
display: block;
height: 10px;
background: #f5f5f5;
}
.bar.money-green {
background: #3cd3ad;
}
.bar {
position: absolute;
display: block;
height: 100%;
background: #fcb31c;
width: 0%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="list">
<div class="item">
<div class="trigger">
<p id="percent">22</p>
</div>
</div>
</div>
<div class="progressbar" data-percentage="">
<div class="money-green bar"><span></span></div>
</div>
I updated the code for you. Clean and clear way
You don't need to return in onclick function. And you should use $(this) inside .each to get current index element
$(".list").on("click", ".trigger", function() {
var e = $(this).closest(".item");
var t = e.find("#percent").text();
$(".progressbar").attr("data-percentage", t);
$(".progressbar").each(function() {
$(this).find(".bar").animate({
width: t + "%"
}, 500);
});
});
See the codepen

How to make this animation properly with jQuery

I am trying to make an animation, you can see my efforts at jsfiddle. And here is the code I've used:
/* JavaScript: */
var app = function () {
var self = this;
var allBoxes = $('.box');
var shadow = $('#shadow');
var busy = false;
self.init = function () {
self.events();
};
self.getBoxLeftPosition = function(id)
{
var left = 100;
if (id == 'box2')
{
left = 300;
} else if (id == 'box3')
{
left = 500;
} else if (id == 'box4')
{
left = 700;
}
return left;
};
self.events = function () {
allBoxes.on('hover mousemove', function(event) {
event.stopPropagation();
var currentBox = $(this);
if (currentBox.hasClass('inactive') && !busy)
{
busy = true;
currentBox.removeClass('inactive').addClass('active').animate({
left: '-=30',
width: 260
}, 400, function () {
busy = false;
});
shadow.fadeIn(400);
}
});
$('body').mousemove(function(event) {
var currentBox = $('.active');
var leftValue = self.getBoxLeftPosition(currentBox.attr('id'));
if (currentBox.length > 0)
{
currentBox.stop();
currentBox.animate({
left: leftValue,
width: 200
}, 300, 'swing', function () {
currentBox.removeClass('active').addClass('inactive');
}, 300);
shadow.fadeOut(300);
}
});
};
return self;
};
var main = new app();
main.init();
/* CSS: */
html, body {
margin: 0;
}
.box {
position: absolute;
top: 120px;
width: 200px;
height: 420px;
}
.box div {
text-align: center;
color: white;
position: absolute;
top: 200px;
left: 0;
right: 0;
font-size: 25px;
font-weight: bold;
}
#box1 {
left: 100px;
background: pink;
}
#box2 {
left: 300px;
background: skyblue;
}
#box3 {
left: 500px;
background: orange;
}
#box4 {
left: 700px;
background: lightgreen;
}
#shadow {
display: none;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: url('https://lh6.googleusercontent.com/Vz0GTzpQVaxmlIvvGgg64CSxcYBbHzu7gMQERduJ4qjU5HAg8KfisFFQvIqvKL5Vn7LIy6HZ=w1920-h916');
z-index: 10;
}
.inactive {
z-index: 5;
}
.active {
z-index: 20;
}
<!-- HTML: -->
<div id="box1" class="box inactive">
<div id="copy1">Copy 1</div>
</div>
<div id="box2" class="box inactive">
<div id="copy2">Copy 2</div>
</div>
<div id="box3" class="box inactive">
<div id="copy3">Copy 3</div>
</div>
<div id="box4" class="box inactive">
<div id="copy4">Copy 4</div>
</div>
<div id="shadow"></div>
Here is what I am trying to achieve in words: I have 4 boxes and whenever a user hovers over one of them that box needs to expand a little and everything else needs to be grayed out and whenever the mouse leaves the box it needs to go back to it's original state.
In my jsfiddle I got it working to a point but it's buggy.
take a look at this JSFiddle
$('.box').hover(function(){
$(this).siblings().addClass('inactive');
}, function(){
$(this).siblings().removeClass('inactive');
});
tried to do it with pure css but sadly there's no such thing as prev sibling selector in css. Its kinda jumpy because of z-index getting immediately change on hover.
You can achieve the same effect even with CSS
$(document).on('mouseenter', '.item', function(e){
var me = $(this);
$('.item').not(this).addClass('greyed');
});
$(document).on('mouseleave', '.item', function(e){
$('.item').removeClass('greyed');
});
ul,li{
list-style:none;padding:0;margin:0;
}
ul{
width:400px;
}
li, .item {
width:100px;
height:100px;
}
li {
float:left;
position:relative;
}
.item {
background-color: #eee;
border:1px solid #ccc;
position:absolute;
z-index:1;
-webkit-transition:all 0.3s ease-in-out;
}
.item:hover {
width:110px;
z-index:2;
}
.red{
background: red;
}
.pink{
background: pink;
}
.blue{
background: blue;
}
.yellow{
background: yellow;
}
.greyed{
-webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */
filter: grayscale(100%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<ul>
<li>
<div class="item red"></div>
</li>
<li>
<div class="item blue"></div>
</li>
<li>
<div class="item yellow"></div>
</li>
<li>
<div class="item pink"></div>
</li>
</ul>
Here is what I did.
First, I found that the background image in your css is not a valid resource, so I replaced it with regular background color (it's transparent black, you may want to adjust it).
Second, I changed allBoxes.on('hover mousemove' to allBoxes.on('mouseover', and $('body').mousemove to allBoxes.on('mouseout'.
Third, I removed the $busy flag tracking.
Fourth, I changed var currentBox = $('.active'); to var currentBox = $(event.target);.
var app = function () {
var self = this;
var allBoxes = $('.box');
var shadow = $('#shadow');
var busy = false;
self.init = function () {
self.events();
};
self.getBoxLeftPosition = function(id)
{
var left = 100;
if (id == 'box2')
{
left = 300;
} else if (id == 'box3')
{
left = 500;
} else if (id == 'box4')
{
left = 700;
}
return left;
};
self.events = function () {
allBoxes.on('mouseover', function(event) {
event.stopPropagation();
var currentBox = $(this);
if (currentBox.hasClass('inactive') && !busy)
{
//busy = true;
currentBox.removeClass('inactive').addClass('active').animate({
left: '-=30',
width: 260
}, 400, function () {
//busy = false;
});
shadow.fadeIn(400);
}
});
allBoxes.on('mouseout', function(event) {
var currentBox = $(event.target);
var leftValue = self.getBoxLeftPosition(currentBox.attr('id'));
if (currentBox.length > 0)
{
currentBox.stop();
currentBox.animate({
left: leftValue,
width: 200
}, 300, 'swing', function () {
currentBox.removeClass('active').addClass('inactive');
}, 300);
shadow.fadeOut(300);
}
});
};
return self;
};
var main = new app();
main.init();
html, body {
margin: 0;
}
.box {
position: absolute;
top: 120px;
width: 200px;
height: 420px;
}
.box div {
text-align: center;
color: white;
position: absolute;
top: 200px;
left: 0;
right: 0;
font-size: 25px;
font-weight: bold;
}
#box1 {
left: 100px;
background: pink;
}
#box2 {
left: 300px;
background: skyblue;
}
#box3 {
left: 500px;
background: orange;
}
#box4 {
left: 700px;
background: lightgreen;
}
#shadow {
display: none;
position: absolute;
left: 0;
top: 0;
width: 1000px;
height: 600px;
/*background: url('https://lh6.googleusercontent.com/Vz0GTzpQVaxmlIvvGgg64CSxcYBbHzu7gMQERduJ4qjU5HAg8KfisFFQvIqvKL5Vn7LIy6HZ=w1920-h916');*/
background-color: rgba(0,0,0,0.5); /* transparent black */
z-index: 10;
}
.inactive {
z-index: 5;
}
.active {
z-index: 20;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="box1" class="box inactive">
<div id="copy1">Copy 1</div>
</div>
<div id="box2" class="box inactive">
<div id="copy2">Copy 2</div>
</div>
<div id="box3" class="box inactive">
<div id="copy3">Copy 3</div>
</div>
<div id="box4" class="box inactive">
<div id="copy4">Copy 4</div>
</div>
<div id="shadow"></div>

Trouble applying CSS changes to dynamically created via JS/JQuery

I've been trying to alter the size of my ".square" divs that are created using JS/JQuery. I've successfully changed the size of the container div, but using the exact same code does not work for the dynamically created .square divs, despite being able to apply events the .square divs in other ways.
I've been trying to understand the problem for the last two days, and have been writing and rewriting solutions, but I think my current skills are overlooking a very simple answer.
The aim was to have the .square divs' size be determined by how many squares will be in the container. The more squares, the smaller the .square div css.
Thanks for any help anyone can give.
$(document).ready(function() {
var create = function(king) {
return $("#container").prepend("<div class='square' id=king></div>");
}
var sLoad = function() {
for (i = 0; i < 16; i++) {
$("#16").click(function() {
$("#container").prepend("<div class='square'></div>");
});
};
};
sLoad();
$("#clear").on("click", function() {
$(".square").remove();
num = prompt("How many squares would you like?");
// var containerSize = function(){
// var siz = 112 * num;
// $("#container").css({"height": siz+15+"px" , "width": siz+"px"});
// }
// containerSize()
$(".square").css({
"height": "50px",
"width": "50px"
});
var make = function(num) {
return num * num;
};
//var squareSize = function(){
// var sqr = 600 / make(num);
// $(".square").css({"height":sqr+"px" , "width":sqr+"px"});
//};
//squareSize();
for (i = 0; i < make(num); i++) {
$("#container").prepend("<div class='square'></div>");
};
});
// $(".button").click(function(){
//
//making the square dis and reappear
$("#container").on("mouseenter", function() {
$(".square").mouseenter(function() {
$(this).fadeTo("fast", 0);
}),
$(".square").mouseleave(function() {
$(this).fadeTo("fast", 1);
});
});
});
#menuContainer {
height: 45px;
width: 50%;
margin: auto;
}
#container {
width: 600px;
height: 600px;
border: 1px blue dotted;
border-radius: 2%;
margin: auto;
padding: 0px;
}
#controlDiv {
height: 30px;
width: 30px;
border: 1px dashed red;
margin: auto;
margin-top: 50%;
background-color: black;
}
.square {
width: 100px;
height: 100px;
background-color: grey;
border: 1px black dashed;
border-radius: 3%;
margin: 5px 5px 5px 5px;
display: inline-block;
}
.button {
height: 27px;
width: 70px;
background-color: gold;
border: solid 1px yellow;
text-decoration-color: blue;
border-radius: 5%;
text-align: center;
padding-top: 7px;
/*margin: auto;*/
margin-bottom: 4px;
display: inline-block;
}
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<div id="menuContainer">
<div class="button" id="16">Click</div>
<div class="button" id="clear">Clear</div>
</div>
<div id="container">
<!-- <div id="controlDiv"></div> -->
</div>
<!--<div class="square"></div>-->
</body>
</html>
This fiddle should work : https://jsfiddle.net/x0x9rv30/2/
You applied the CSS on removed elements, you need to create the elements first, then you can apply CSS on it.
I just swapped two code blocks :
Before
$(".square").remove();
$(".square").css({"height":"50px" , "width": "50px"});
for (i = 0; i < make(num); i++){
$("#container").prepend("<div class='square'></div>");
};
After
$(".square").remove();
for (i = 0; i < make(num); i++){
$("#container").prepend("<div class='square'></div>");
};
$(".square").css({"height":"50px" , "width": "50px"});

A javascript slider array issue

Having a slider with images implementation from array, cant figure out why images dont want to be shown up from array, tryed to make a path but it didnt work.I want this code to reflect this image every time a push the button: fpoimg.com/100x100.
Im trying to fix it only with clean javascript.
Here is a sandbox
var slider = {
slides: ['100x100', '100x100', '100x100', '100x100'],
frame:0,
set:function(image){
path = path || 'http://fpoimg.com/';
document.getElementById('scr').style.backgroundImage ="url ("+path+ image+")";
},
init:function() {
this.set(this.slides[this.frame]);
},
left:function() {
this.frame--;
if(frame < 0) this.frame = this.slides.length - 1;
this.set(this.slides[this.frame]);
},
right:function() {
if(this.frame == this.slides.length) this.frame = 0;
this.set(this.slides[this.frame]);
}
};
window.onload = function() {
slider.init();
setInterval(function() {
slider.right();
},5000);
};
.scr {
margin:20px auto;
width: 600px;
height: 320px;
margin-top:20px;
background-color: white;
background-size:cover;
}
button {
position: absolute;
top: 150px;
width: 25px;
height: 150px;
font-size: 30px;
text-align: center;
background:none;
border:none;
}
.left {
left:25px;
}
.right {
right:25px;
}
<body>
<button class="left" onclick="slider.left();"><</button>
<div class="scr"></div>
<button class="right" onclick="slider.right();">></button>
</body>
On Line 6 of your Javascript, you have used getElementById('scr'). You have no element with an Id or scr, you needed to use getElementsByClassName('scr')
Your new code:
var slider = {
slides: ['100x100', '100x100', '100x100', '100x100'],
frame: 0,
set: function(image) {
path = path || 'http://fpoimg.com/';
document.getElementsByClassName('scr').style.backgroundImage = "url (" + path + image + ")";
},
init: function() {
this.set(this.slides[this.frame]);
},
left: function() {
this.frame--;
if (frame < 0) this.frame = this.slides.length - 1;
this.set(this.slides[this.frame]);
},
right: function() {
if (this.frame == this.slides.length) this.frame = 0;
this.set(this.slides[this.frame]);
}
};
window.onload = function() {
slider.init();
setInterval(function() {
slider.right();
}, 5000);
};
.scr {
margin: 20px auto;
width: 600px;
height: 320px;
margin-top: 20px;
background-color: white;
background-size: cover;
}
button {
position: absolute;
top: 150px;
width: 25px;
height: 150px;
font-size: 30px;
text-align: center;
background: none;
border: none;
}
.left {
left: 25px;
}
.right {
right: 25px;
}
<body>
<button class="left" onclick="slider.left();">
</button>
<div class="scr"></div>
<button class="right" onclick="slider.right();"></button>
</body>
It seems you've got getElementById() when you meant getElementsByClassName()

Categories