Placing interactive box on top of the canvas on mouseleave - javascript

I'm trying to make a box follow the cursor on mousemove. However, it should stop following the mouse if
the user clicks the box
the user's mouse leaves the surrounding div
Here's what the current setup looks like:
$(function() {
$(document).mousemove(function(e) {
// assign the co-ords as vars so
var vert = e.pageY;
var horz = e.pageX;
// get co -ords for the center of the box
centx = Math.ceil($('div').width() / 2);
centy = Math.ceil($('div').height() / 2);
// checking to see if the cursor is outside the boudries of the box and stoping the circle moving past them
if (e.pageY + 53 > Math.ceil($('div').height())) var vert = Math.ceil($('div').height() - 47);
if (e.pageY - 53 < 0) vert = 53;
if (e.pageX + 53 > Math.ceil($('div').width())) var horz = Math.ceil($('div').width() - 47);
if (e.pageX - 53 < 0) horz = 53;
// work out the distance between the cursor and the center of box
disx = horz - centx;
disy = vert - centy;
// work out the scale of the shadow
sx = -disx * 0.1;
sy = -disy * 0.1;
// work out the shadow blur
b = (Math.abs(sx) + Math.abs(sy)) * 0.5;
//apply the css
$('p').css({
'top': vert - 53,
'left': horz - 53,
'-webkit-box-shadow': '#a0a0a0 ' + sx + 'px ' + sy + 'px ' + b + 'px, inset ' + sx + 'px ' + sy + 'px ' + b + 'px #e3e3e3'
});
});
});
body {
padding: 0px;
}
div {
position: relative;
width: 400px;
height: 400px;
background-color: #f0f0f0;
}
p {
background-color: orange;
height: 100px;
width: 100px;
position: absolute;
top: 100px;
left: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<div>
<p>set the location</p>
</div>
fiddle

edit
To restrict movement once the box is clicked, just use a helper variable:
var cond = false;
$(yourElement).click(function () {
cond = true;
})
Then, in the body of your mousemove function:
if (cond) return false;
You should check whether the mouse is within the div using Element.getBoundingClientRect:
var boundary = document.getElementById("theDiv").getBoundingClientRect()
var vert = e.pageY;
var horz = e.pageX;
if (boundary.top <= vert && vert <= boundary.bottom &&
boundary.left <= horz && horz <= boundary.right) {
/* the rest of your code */
}
Element.getBoundingClientRect returns an object like this:
{
bottom: 408,
height: 400,
left: 8,
right: 408,
top: 8,
width: 400,
}
If you want to use jQuery, you can instead use
var left = $(item).offset().left
, top = $(item).offset().top
, right = left + $(item).width()
, bottom = top + $(item).height()
;
and set up a similar if condition.
demo:
$(function() {
var cond = false;
$("#theDiv").click(function () {
cond = true;
})
$(document).mousemove(function(e) {
if (cond) return false;
var boundary = document.getElementById("theDiv").getBoundingClientRect()
// assign the co-ords as vars so
var vert = e.pageY;
var horz = e.pageX;
if (boundary.top <= vert && vert <= boundary.bottom && boundary.left <= horz && horz <= boundary.right) {
// get co -ords for the center of the box
centx = Math.ceil($('div').width() / 2);
centy = Math.ceil($('div').height() / 2);
// checking to see if the cursor is outside the boudries of the box and stoping the circle moving past them
if (e.pageY + 53 > Math.ceil($('div').height())) var vert = Math.ceil($('div').height() - 47);
if (e.pageY - 53 < 0) vert = 53;
if (e.pageX + 53 > Math.ceil($('div').width())) var horz = Math.ceil($('div').width() - 47);
if (e.pageX - 53 < 0) horz = 53;
// work out the distance between the cursor and the center of box
disx = horz - centx;
disy = vert - centy;
// work out the scale of the shadow
sx = -disx * 0.1;
sy = -disy * 0.1;
// work out the shadow blur
b = (Math.abs(sx) + Math.abs(sy)) * 0.5;
//apply the css
$('p').css({
'top': vert - 53,
'left': horz - 53,
'-webkit-box-shadow': '#a0a0a0 ' + sx + 'px ' + sy + 'px ' + b + 'px, inset ' + sx + 'px ' + sy + 'px ' + b + 'px #e3e3e3'
});
}
});
});
body {
padding: 0px;
}
div {
position: relative;
width: 400px;
height: 400px;
background-color: #f0f0f0;
}
p {
background-color: orange;
height: 100px;
width: 100px;
position: absolute;
top: 100px;
left: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="theDiv">
<p>set the location</p>
</div>
fiddle

Related

Drawing a path between points in javascript/react/css

I am trying to draw a path between points wihtout using canvas or any other librares.
Everything works fine for positive degrees, but it doesn't for negative..
My "points" are 150x150 [px], that's why there is that +75px in left position.
This is my code:
useEffect(() => {
let x1 = spider1.x;
let y1 = spider1.y;
let x2 = spider2.x;
let y2 = spider2.y;
if (x1 > x2) {
let xTmp = x1;
let yTmp = y1;
x1 = x2;
y1 = y2;
x2 = xTmp;
y2 = yTmp;
}
if (x1 === x2) {
const height = Math.abs(y2 - y1).toString();
setStyle({
width: '5px',
left: (x1 + 75).toString() + 'px',
top: `${Math.min(y1, y2) + 75}px`,
height: `${height}px`,
transform: 'rotate(0deg)'
});
} else {
const a = (y2 - y1) / (x2 - x1);
const radians = Math.atan(a);
const degrees = radians * (180 / Math.PI);
setStyle({
width: `${Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))}px`,
left: (x1 + 75).toString() + 'px',
top: `${Math.min(y1, y2) + 75}px`,
height: `5px`,
transform: `rotate(${degrees}deg)`,
transformOrigin: `${degrees > 0 ? '0% 0%' : '100% 100%'}`
});
}
}, [spider1, spider2]);
return <div className='net' style={style} ref={myRef}></div>;
And the net class:
.net {
position: absolute;
background-color: red;
z-index: 90;
}
On the screens we have the same objects, but the degrees on the second one are "-" and the line doesn't connect that two squares because of that
This example works. You will have to compare yours with mine as to what the differences are.
Note: I just wrote it from scratch rather using your example.
Just click "Random position"
var button = document.getElementById("new-points");
button.onclick = function() {
var xy1 = {
m_x : Math.floor((Math.random() * 200) + 1),
m_y : Math.floor((Math.random() * 200) + 1),
};
var xy2 = {
m_x : Math.floor((Math.random() * 200) + 1),
m_y : Math.floor((Math.random() * 200) + 1),
};
var dotA = document.getElementById("dotA");
dotA.style.left = (xy1.m_x - 5) + "px";
dotA.style.top = (xy1.m_y - 5) + "px";
var dotB = document.getElementById("dotB");
dotB.style.left = (xy2.m_x - 5) + "px";
dotB.style.top = (xy2.m_y - 5) + "px";
var line = document.getElementById("line");
var distance = Math.hypot(xy2.m_x - xy1.m_x, xy2.m_y - xy1.m_y);
line.style.width = distance + "px";
line.style.left = xy1.m_x + "px";
line.style.top = xy1.m_y + "px";
var angle = ((Math.atan2(xy1.m_x - xy2.m_x, xy2.m_y - xy1.m_y) + (Math.PI / 2.0)) * 180 / Math.PI);
line.style.transform = "rotate(" + angle + "deg)";
line.style.display = "block";
}
#new-points {
border: 1px solid black;
padding: 5px;
width: 200px;
text-align: center;
}
.dot {
position: absolute;
width: 10px;
height: 10px;
background: blue;
}
#line {
position: absolute;
border-top: solid 1px red;
height: 1px;
max-height: 1px;
transform-origin: 0% 0%;
}
<div id="dotA" class="dot"></div>
<div id="dotB" class="dot"></div>
<div id="line"></div>
<div id="new-points" style="">
Random position
</div>
I've also tried the atan method for the above and it doesn't work. You can try it yourself and see what happens.

Gradient follows mouse but doesn't catch up

I've put together a simple jQuery script to create a radial gradient that follows the mouse with a delay, but since it's in a mousemove function, when the mouse stops moving the gradient doesn't catch up. Is there a simple way to make the gradient catch up with the mouse when the mouse stops without writing a function that's constantly running?
var xPos = 0;
var yPos = 0;
var dX = 0;
var dY = 0;
var repeater;
$(document).mousemove(function(event) {
windowWidth = $(window).width();
windowHeight = $(window).height();
mouseXpercentage = Math.round(event.pageX / windowWidth * 100);
mouseYpercentage = Math.round(event.pageY / windowHeight * 100);
dX = mouseXpercentage - xPos;
dY = mouseYpercentage - yPos;
xPos += (dX / 50);
yPos += (dY / 50);
$('.rgradient').css('background', 'radial-gradient(at ' + xPos + '% ' + yPos + '%, #e6e6e6, #1e1e1e)');
});
.rgradient {
position: fixed;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
background: #1e1e1e;
background: radial-gradient( at center, #e6e6e6 #1e1e1e);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="rgradient"></div>
You can use requestAnimationFrame(), as it only runs once for each frame:
var xPos = 0;
var yPos = 0;
var dX = 0;
var dY = 0;
var mouseRaf = null;
var gradMoveRaf = null;
$(document).mousemove(function(event) {
if (!mouseRaf) {
mouseRaf = requestAnimationFrame(function() {
windowWidth = $(window).width();
windowHeight = $(window).height();
mouseXpercentage = Math.round(event.pageX / windowWidth * 100);
mouseYpercentage = Math.round(event.pageY / windowHeight * 100);
dX = mouseXpercentage - xPos;
dY = mouseYpercentage - yPos;
mouseRaf = null;
});
}
if (!gradMoveRaf) {
gradMoveRaf = requestAnimationFrame(gradMove);
}
});
function gradMove() {
xPos += (dX / 50);
yPos += (dY / 50);
$('.rgradient').css('background', 'radial-gradient(at ' + xPos + '% ' + yPos + '%, #e6e6e6, #1e1e1e)');
var absX = Math.abs(mouseXpercentage - xPos);
var absY = Math.abs(mouseYpercentage - yPos);
if (absX < 1 && absY < 1) {
gradMoveRaf = null;
console.log("stop");
} else {
gradMoveRaf = requestAnimationFrame(gradMove);
console.log("repeat");
}
}
.rgradient {
position: fixed;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
background: #1e1e1e;
background: radial-gradient(at center, #e6e6e6 #1e1e1e);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="rgradient"></div>

Making div appear from off-screen

I'm doing this assignment where I have to use pure javascript to make a div appear from the side of the screen without having the browser expand. This is what I have so far:
function moveImg() {
var div = document.getElementsByTagName('div')[0];
var pos = 500;
var id = setInterval(Move, 500);
function Move() {
if (pos <= 0) {
clearInterval(id);
} else {
pos++;
div.style.right = pos + 'px';
div.style.top = pos + 'px';
}
}
}
moveImg()
div {
width: 50px;
height: 50px;
background: tomato;
}
<div></div>
I'd use transform generally.
I've done some maths too, I've commented to code but feel free to ask if there's anything you need help understanding.
function moveImg() {
var div = document.getElementsByTagName('div')[0];
var posX = document.body.clientWidth - div.offsetWidth; // start position for X
var posY = document.body.clientHeight - div.offsetHeight; // start position for Y
var tarX = posX / 2; // end position for X
var tarY = posY / 2; // end position for Y
var time = 5; // how many seconds should the animation take
var fps = 60; // frames per second, heigher is smoother but requires more power
var stepX = ((posX - tarX) / (time * fps)); // how far X should move each frame
var stepY = ((posY - tarY) / (time * fps)); // how far Y should move each frame
var id = setInterval(Move, (1000 / fps));
div.style.transform = "translate(" + posX + "px, " + posY + "px)";
div.style.opacity = "1";
function Move() {
if (posX <= tarX && posY <= tarY) {
clearInterval(id);
} else {
posX -= stepX;
posY -= stepY;
div.style.transform = "translate(" + posX + "px, " + posY + "px)";
}
}
}
moveImg();
body {
margin: 0;
width: 100vw;
height: 100vh;
}
div {
display: inline-block;
color: white;
padding: 15px 30px;
background: tomato;
opacity: 0;
}
<div>Example!</div>
Like what was said in the comments, you don't say in what direction you want your div to move, nor from which position it should start.
I've set the starting position of top:100, left:100 and reducing the top and left values by 1px each second:
function moveImg() {
var div = document.getElementsByTagName('div')[0];
var pos = 100;
var id = setInterval(Move, 500);
function Move() {
if (pos <= 0) {
clearInterval(id);
} else {
pos--;
div.style.left = pos + 'px';
div.style.top = pos + 'px';
}
}
}
moveImg()
div {
width: 50px;
height: 50px;
background: tomato;
position: relative;
top: 100px;
left: 100px;
}
<div></div>

How to increase FPS parallax plugin?

In the script, the picture is very slow.
$(".holiday-face__item").each(function(i, el) {
var offset = parseInt($(el).data('offset'));
var translate = "translate3d(0px," + Math.round(offsetY * offset) + "px, 0px)";
var x = (e.pageX * -1 / 40),
y = (e.pageY * -1 / 25);
$(el).css({
'-webkit-transform': translate,
'transform': translate,
'moz-transform': translate
});
});
$(".holiday-face_parallax .holiday-face__item_5").each(function(i, el) {
var x = (e.pageX * -1 / 40), y = (e.pageY * -1 / 25);
$(this).css('background-position', x + 'px ' + y + 'px');
});
$(".holiday-face_parallax .holiday-face__item_4").each(function(i, el) {
var x = (e.pageX * -1 / 30), y = (e.pageY * -1 / 20);
$(this).css('background-position', x + 'px ' + y + 'px');
});
$(".holiday-face_parallax .holiday-face__item_3").each(function(i, el) {
var x = (e.pageX * -1 / 40), y = (e.pageY * -1 / 25);
$(this).css('background-position', x + 'px ' + y + 'px');
});
$(".holiday-face_parallax .holiday-face__item_2").each(function(i, el) {
var x = (e.pageX * -1 / 25), y = (e.pageY * -1 / 25);
$(this).css('background-position', x + 'px ' + y + 'px');
});
$(".holiday-face_parallax .holiday-face__item_1").each(function(i, el) {
var x = (e.pageX * -1 / 20), y = (e.pageY * -1 / 25);
$(this).css('background-position', x + 'px ' + y + 'px');
});
});
.holiday-face {
bottom: 0;
left: 0;
min-height: 540px;
min-width: 875px;
right: 0;
top: 0;
z-index: -1;
}
.holiday-face_parallax .holiday-face__item_5 {
background-image: url("../img/header-bg-ley2.png");
width: 100%;
height: 600px;
}
.holiday-face_parallax .holiday-face__item_4 {
width: 100%;
height: 600px;
background-image: url("../img/header-bg-ley3.png");
}
.holiday-face_parallax .holiday-face__item_3 {
background-image: url("../img/header-bg-ley4.png");
width: 100%;
height: 600px;
}
.holiday-face_parallax .holiday-face__item_2 {
background-image: url("../img/header-bg-ley5.png");
width: 100%;
height: 600px;
}
.holiday-face_parallax .holiday-face__item_1 {
background-image: url("../img/header-bg-ley1.png");
width: 100%;
height: 600px;
}
.holiday-face-words__link {
height: 100%;
left: 0;
margin: 0 auto;
position: absolute;
right: 0;
}
.holiday-face-words {
background: none no-repeat scroll center top rgba(0, 0, 0, 0);
height: 69px;
left: 0px;
position: absolute;
right: 0px;
top: 0%;
width:100%;
}
.b-page_holiday-face .holiday-face_parallax .holiday-face__item {
opacity: 1;
}
.holiday-face__item {
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
bottom: 0;
left: 0;
margin: 0px 0;
position: absolute;
right: 0;
top: 0;
transition: opacity 0.1s ease 0s;
}
<div data-id="1" class="holiday-face holiday-face_parallax">
<div data-offset="20" class="holiday-face__item holiday-face__item_5"></div>
<div data-offset="70" class="holiday-face__item holiday-face__item_4"></div>
<div data-offset="30" class="holiday-face__item holiday-face__item_3"></div>
<div data-offset="20" class="holiday-face__item holiday-face__item_2"></div>
<div data-offset="50" class="holiday-face__item holiday-face__item_1"></div>
</div>
<div data-id="1" class="holiday-face holiday-face_parallax">
<div data-offset="20" class="holiday-face__item holiday-face__item_5"></div>
<div data-offset="70" class="holiday-face__item holiday-face__item_4"></div>
<div data-offset="30" class="holiday-face__item holiday-face__item_3"></div>
<div data-offset="20" class="holiday-face__item holiday-face__item_2"></div>
<div data-offset="50" class="holiday-face__item holiday-face__item_1"></div>
</div>
$(".holiday-face__item").each(function(i, el) {
var offset = parseInt($(el).data('offset'));
var translate = "translate3d(0px," + Math.round(offsetY * offset) + "px, 0px)";
var x = (e.pageX * -1 / 40),
y = (e.pageY * -1 / 25);
$(el).css({
'-webkit-transform': translate,
'transform': translate,
'moz-transform': translate
});
});
$(".holiday-face_parallax .holiday-face__item_5").each(function(i, el) {
var x = (e.pageX * -1 / 40), y = (e.pageY * -1 / 25);
$(this).css('background-position', x + 'px ' + y + 'px');
});
$(".holiday-face_parallax .holiday-face__item_4").each(function(i, el) {
var x = (e.pageX * -1 / 30), y = (e.pageY * -1 / 20);
$(this).css('background-position', x + 'px ' + y + 'px');
});
$(".holiday-face_parallax .holiday-face__item_3").each(function(i, el) {
var x = (e.pageX * -1 / 40), y = (e.pageY * -1 / 25);
$(this).css('background-position', x + 'px ' + y + 'px');
});
$(".holiday-face_parallax .holiday-face__item_2").each(function(i, el) {
var x = (e.pageX * -1 / 25), y = (e.pageY * -1 / 25);
$(this).css('background-position', x + 'px ' + y + 'px');
});
$(".holiday-face_parallax .holiday-face__item_1").each(function(i, el) {
var x = (e.pageX * -1 / 20), y = (e.pageY * -1 / 25);
$(this).css('background-position', x + 'px ' + y + 'px');
});
});
It looks like the values of
var x = (e.pageX * -1 / 30), y = (e.pageY * -1 / 20);
define the speed of the parallax. Your example does not work correctly, so I cannot test this out for sure.
Try changing the values of these assignments. A smaller value should make it go slower, where a larger value will make it faster.
To make it very slow, for example, try
var x = (e.pageX * -1 / 3000), y = (e.pageY * -1 / 2000);
Looks like each function gets its own speeds, so you can choose how fast or slow you want each photo to go.

Move a coordinate along an angled line using mouse movement

Please run the code snippet below - you will see an angled line derived from the corners of a box. Given mouse movement, I would like to move the marker along this line - where either:
movement in x will move along the line, and additional movement in y will accelerate
movement in x will move along the line and you can get from start to finish with it (whatever is simplest)
So far I haven't got anywhere but I'll update if I do and many thanks for any help.
PS doesn't really matter if it doesn't move exactly along the line - it's the algorithm to move along an angle I'm interested in.
Fiddle: http://jsfiddle.net/ngr3dbhx/3/
var boxEl = document.getElementById('box');
var lineEl = document.getElementById('line');
var markerEl = document.getElementById('marker');
var rad, deg;
// Draw an angled line for demonstration purposes.
function getElementOffset (el) {
var rect = el.getBoundingClientRect();
var docEl = document.documentElement;
var rectTop = rect.top + window.pageYOffset - docEl.clientTop;
var rectLeft = rect.left + window.pageXOffset - docEl.clientLeft;
return {
top: rectTop,
left: rectLeft
};
}
function calcAndDrawAngle () {
var boxOffset = getElementOffset(boxEl);
var x1 = boxOffset.left;
var y1 = boxOffset.top;
var x2 = boxOffset.left + boxEl.offsetWidth;
var y2 = boxOffset.top + boxEl.offsetHeight;
var deltaX = x2 - x1;
var deltaY = y2 - y1;
rad = Math.atan2(deltaY, deltaX);
deg = rad * (180 / Math.PI);
lineEl.style.transform = 'rotate(' + deg + 'deg) translate(-50%, 0)';
}
// On mouse move I want to move the marker along the anged line..
// Do something with rad or deg?
document.addEventListener('mousemove', function (e) {
markerEl.style.top = e.clientY + 'px';
markerEl.style.left = e.clientX + 'px';
});
calcAndDrawAngle();
window.addEventListener('resize', calcAndDrawAngle);
html,
body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#box {
position: absolute;
top: 20%;
left: 30%;
width: 35%;
height: 30%;
border: 1px solid #999;
}
#line {
position: absolute;
top: 0;
left: 0;
transform-origin: top left;
height: 1px;
width: 9999em;
background-color: black;
}
#marker {
position: absolute;
width: 8px;
height: 8px;
background-color: red;
}
<div id="box">
<div id="line"></div>
</div>
<div id="marker"></div>
you mean this?
https://jsfiddle.net/2q1nLh3q/1/
The formula is basic mathematics:
y = k * x + d
Where k is deltaY / deltaX and d is the point where the line crosses the y axis.
So your function could look like this:
document.addEventListener('mousemove', function (e) {
window.requestAnimationFrame(function() {
var boxOffset = getElementOffset(boxEl);
var k = boxEl.offsetHeight / boxEl.offsetWidth;
var d = boxOffset.top - boxOffset.left * k;
var mouseY = k * e.clientX + d;
markerEl.style.top = mouseY + 'px';
markerEl.style.left = e.clientX + 'px';
});
});
Using fairly simple math, calculate y for any x within limited range (subtracted 4 to place rectangle at center):
//var canvas = document.getElementById('canvas');
var boxEl = document.getElementById('box');
var lineEl = document.getElementById('line');
var markerEl = document.getElementById('marker');
var rad, deg;
var coords;
// Draw an angled line for demonstration purposes.
function getElementOffset (el) {
var rect = el.getBoundingClientRect();
var docEl = document.documentElement;
var rectTop = rect.top + window.pageYOffset - docEl.clientTop;
var rectLeft = rect.left + window.pageXOffset - docEl.clientLeft;
return {
top: rectTop,
left: rectLeft
};
}
function calcAndDrawAngle () {
var boxOffset = getElementOffset(boxEl);
var x1 = boxOffset.left;
var y1 = boxOffset.top;
var x2 = boxOffset.left + boxEl.offsetWidth;
var y2 = boxOffset.top + boxEl.offsetHeight;
coords = [{x:x1,y:y2},{x:x2,y:y1}];
var deltaX = x2 - x1;
var deltaY = y2 - y1;
rad = Math.atan2(deltaY, deltaX);
deg = rad * (180 / Math.PI);
lineEl.style.transform = 'rotate(' + deg + 'deg) translate(-50%, 0)';
}
// On mouse move I want to move the marker along the anged line..
// Do something with rad or deg?
document.addEventListener('mousemove', function (e) {
var x = Math.min(Math.max(e.clientX, coords[0].x), coords[1].x) - 4;
var y = coords[0].y + ((coords[1].y - coords[0].y) * (coords[1].x - x) / (coords[1].x - coords[0].x));
markerEl.style.top = y + 'px';
markerEl.style.left = x + 'px';
});
calcAndDrawAngle();
window.addEventListener('resize', calcAndDrawAngle);
html,
body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#box {
position: absolute;
top: 20%;
left: 30%;
width: 35%;
height: 30%;
border: 1px solid #999;
}
#line {
position: absolute;
top: 0;
left: 0;
transform-origin: top left;
height: 1px;
width: 9999em;
background-color: black;
}
#marker {
position: absolute;
width: 8px;
height: 8px;
background-color: red;
}
<div id="box">
<div id="line"></div>
</div>
<div id="marker"></div>

Categories