This commit is contained in:
Ilya Kantor 2017-03-09 00:48:54 +03:00
parent d85be5f17b
commit fc84391bd2
143 changed files with 6874 additions and 1 deletions

View file

@ -0,0 +1,121 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
body {
height: 2000px;
/* the tooltip should work after page scroll too */
}
.tooltip {
position: fixed;
z-index: 100;
padding: 10px 20px;
border: 1px solid #b3c9ce;
border-radius: 4px;
text-align: center;
font: italic 14px/1.3 arial, sans-serif;
color: #333;
background: #fff;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
}
#house {
margin-top: 50px;
width: 400px;
border: 1px solid brown;
}
#roof {
width: 0;
height: 0;
border-left: 200px solid transparent;
border-right: 200px solid transparent;
border-bottom: 20px solid brown;
margin-top: -20px;
}
p {
text-align: justify;
margin: 10px 3px;
}
</style>
</head>
<body>
<div data-tooltip="Here is the house interior" id="house">
<div data-tooltip="Here is the roof" id="roof"></div>
<p>Once upon a time there was a mother pig who had three little pigs.</p>
<p>The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."
<p>The three little pigs set off. "We will take care that the wolf does not catch us," they said.</p>
<p>Soon they met a man. <a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Read on…">Hover over me</a></p>
</div>
<script>
let tooltip;
document.onmouseover = function(event) {
// important: a fast-moving mouse may "jump" right to a child on an annotated node, skipping the parent
// so mouseover may happen on a child.
let anchorElem = event.target.closest('[data-tooltip]');
if (!anchorElem) return;
// show tooltip and remember it
tooltip = showTooltip(anchorElem, anchorElem.dataset.tooltip);
}
document.onmouseout = function() {
// возможно такое, что mouseout сработал, а мы все еще внутри элемента (всплытие)
// но в этом случае сразу же будет и mouseover,
// то есть подсказка будет уничтожена и тут же показана заново
//
// это лишние расходы, их можно избежать дополнительными проверками
if (tooltip) {
tooltip.remove();
tooltip = false;
}
}
function showTooltip(anchorElem, html) {
let tooltipElem = document.createElement('div');
tooltipElem.className = 'tooltip';
tooltipElem.innerHTML = html;
document.body.append(tooltipElem);
let coords = anchorElem.getBoundingClientRect();
// position the tooltip over the center of the element
let left = coords.left + (anchorElem.offsetWidth - tooltipElem.offsetWidth) / 2;
if (left < 0) left = 0;
var top = coords.top - tooltipElem.offsetHeight - 5;
if (top < 0) {
top = coords.top + anchorElem.offsetHeight + 5;
}
tooltipElem.style.left = left + 'px';
tooltipElem.style.top = top + 'px';
return tooltipElem;
}
</script>
</body>
</html>

View file

@ -0,0 +1,70 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
body {
height: 2000px;
/* the tooltip should work after page scroll too */
}
.tooltip {
position: fixed;
z-index: 100;
padding: 10px 20px;
border: 1px solid #b3c9ce;
border-radius: 4px;
text-align: center;
font: italic 14px/1.3 arial, sans-serif;
color: #333;
background: #fff;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
}
#house {
margin-top: 50px;
width: 400px;
border: 1px solid brown;
}
#roof {
width: 0;
height: 0;
border-left: 200px solid transparent;
border-right: 200px solid transparent;
border-bottom: 20px solid brown;
margin-top: -20px;
}
p {
text-align: justify;
margin: 10px 3px;
}
</style>
</head>
<body>
<div data-tooltip="Here is the house interior" id="house">
<div data-tooltip="Here is the roof" id="roof"></div>
<p>Once upon a time there was a mother pig who had three little pigs.</p>
<p>The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."
<p>The three little pigs set off. "We will take care that the wolf does not catch us," they said.</p>
<p>Soon they met a man. <a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Read on…">Hover over me</a></p>
</div>
<script>
// ...your code...
</script>
</body>
</html>

View file

@ -0,0 +1,25 @@
importance: 5
---
# Improved tooltip behavior
Write JavaScript that shows a tooltip over an element with the attribute `data-tooltip`.
That's like the task <info:task/behavior-tooltip>, but here the annotated elements can be nested. The most deeply nested tooltip is shown.
For instance:
```html
<div data-tooltip="Here is the house interior" id="house">
<div data-tooltip="Here is the roof" id="roof"></div>
...
<a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Read on…">Hover over me</a>
</div>
```
The result in iframe:
[iframe src="solution" height=300 border=1]
P.S. Hint: only one tooltip may show up at the same time.

View file

@ -0,0 +1,18 @@
The algorithm looks simple:
1. Put `onmouseover/out` handlers on the element. Also can use `onmouseenter/leave` here, but they are less universal, won't work if we introduce delegation.
2. When a mouse cursor entered the element, start measuring the speed on `mousemove`.
3. If the speed is slow, then run `over`.
4. Later if we're out of the element, and `over` was executed, run `out`.
The question is: "How to measure the speed?"
The first idea would be: to run our function every `100ms` and measure the distance between previous and new coordinates. If it's small, then the speed is small.
Unfortunately, there's no way to get "current mouse coordinates" in JavaScript. There's no function like `getCurrentMouseCoordinates()`.
The only way to get coordinates is to listen to mouse events, like `mousemove`.
So we can set a handler on `mousemove` to track coordinates and remember them. Then we can compare them, once per `100ms`.
P.S. Please note: the solution tests use `dispatchEvent` to see if the tooltip works right.

View file

@ -0,0 +1,106 @@
'use strict';
class HoverIntent {
constructor({
sensitivity = 0.1, // speed less than 0.1px/ms means "hovering over an element"
interval = 100, // measure mouse speed once per 100ms
elem,
over,
out
}) {
this.sensitivity = sensitivity;
this.interval = interval;
this.elem = elem;
this.over = over;
this.out = out;
// make sure "this" is the object in event handlers.
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseOver = this.onMouseOver.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);
// and in time-measuring function (called from setInterval)
this.trackSpeed = this.trackSpeed.bind(this);
elem.addEventListener("mouseover", this.onMouseOver);
elem.addEventListener("mouseout", this.onMouseOut);
}
onMouseOver(event) {
if (this.isOverElement) {
// if we're over the element, then ignore the event
// we are already measuring the speed
return;
}
this.isOverElement = true;
// after every mousemove we'll be check the distance
// between the previous and the current mouse coordinates
// if it's less than sensivity, then the speed is slow
this.prevX = event.pageX;
this.prevY = event.pageY;
this.prevTime = Date.now();
elem.addEventListener('mousemove', this.onMouseMove);
this.checkSpeedInterval = setInterval(this.trackSpeed, this.interval);
}
onMouseOut(event) {
// if left the element
if (!event.relatedTarget || !elem.contains(event.relatedTarget)) {
this.isOverElement = false;
this.elem.removeEventListener('mousemove', this.onMouseMove);
clearInterval(this.checkSpeedInterval);
if (this.isHover) {
// если была остановка над элементом
this.out.call(this.elem, event);
this.isHover = false;
}
}
}
onMouseMove(event) {
this.lastX = event.pageX;
this.lastY = event.pageY;
this.lastTime = Date.now();
}
trackSpeed() {
let speed;
if (!this.lastTime || this.lastTime == this.prevTime) {
// cursor didn't move
speed = 0;
} else {
speed = Math.sqrt(
Math.pow(this.prevX - this.lastX, 2) +
Math.pow(this.prevY - this.lastY, 2)
) / (this.lastTime - this.prevTime);
}
if (speed < this.sensitivity) {
clearInterval(this.checkSpeedInterval);
this.isHover = true;
this.over.call(this.elem, event);
} else {
// speed fast, remember new coordinates as the previous ones
this.prevX = this.lastX;
this.prevY = this.lastY;
this.prevTime = this.lastTime;
}
}
destroy() {
elem.removeEventListener('mousemove', this.onMouseMove);
elem.removeEventListener('mouseover', this.onMouseOver);
elem.removeEventListener('mouseout', this.onMouseOut);
};
}

View file

@ -0,0 +1,40 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
<script src="hoverIntent.js"></script>
<script src="https://en.js.cx/test/libs.js"></script>
<script src="test.js"></script>
</head>
<body>
<div id="elem" class="clock">
<span class="hours">12</span> :
<span class="minutes">30</span> :
<span class="seconds">00</span>
</div>
<div id="tooltip" hidden>Tooltip</div>
<script>
// for the demo
setTimeout(function() {
new HoverIntent({
elem,
over() {
tooltip.style.left = elem.getBoundingClientRect().left + 5 + 'px';
tooltip.style.top = elem.getBoundingClientRect().bottom + 5 + 'px';
tooltip.hidden = false;
},
out() {
tooltip.hidden = true;
}
});
}, 2000);
</script>
</body>
</html>

View file

@ -0,0 +1,38 @@
.hours {
color: red;
}
body#mocha {
margin: 0;
}
.minutes {
color: green;
}
.seconds {
color: blue;
}
.clock {
border: 1px dashed black;
padding: 5px;
display: inline-block;
background: yellow;
position: absolute;
left: 0;
top: 0;
}
#tooltip {
position: absolute;
padding: 10px 20px;
border: 1px solid #b3c9ce;
border-radius: 4px;
text-align: center;
font: italic 14px/1.3 arial, sans-serif;
color: #333;
background: #fff;
z-index: 100000;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
}

View file

@ -0,0 +1,98 @@
'use strict';
describe("hoverIntent", function() {
function mouse(eventType, x, y, options) {
let eventOptions = Object.assign({
bubbles: true,
clientX: x,
clientY: y,
pageX: x,
pageY: y,
target: elem
}, options || {});
elem.dispatchEvent(new MouseEvent(eventType, eventOptions));
}
let isOver;
let hoverIntent;
before(function() {
this.clock = sinon.useFakeTimers();
});
after(function() {
this.clock.restore();
});
beforeEach(function() {
isOver = false;
hoverIntent = new HoverIntent({
elem: elem,
over: function() {
isOver = true;
},
out: function() {
isOver = false;
}
});
})
afterEach(function() {
if (hoverIntent) {
hoverIntent.destroy();
}
})
it("mouseover -> immediately no tooltip", function() {
mouse('mouseover', 10, 10);
assert.isFalse(isOver);
});
it("mouseover -> pause shows tooltip", function() {
mouse('mouseover', 10, 10);
this.clock.tick(100);
assert.isTrue(isOver);
});
it("mouseover -> fast mouseout no tooltip", function() {
mouse('mouseover', 10, 10);
setTimeout(
() => mouse('mouseout', 300, 300, { relatedTarget: document.body}),
30
);
this.clock.tick(100);
assert.isFalse(isOver);
});
it("mouseover -> slow move -> tooltips", function() {
mouse('mouseover', 10, 10);
for(let i=10; i<200; i+= 10) {
setTimeout(
() => mouse('mousemove', i/5, 10),
i
);
}
this.clock.tick(200);
assert.isTrue(isOver);
});
it("mouseover -> fast move -> no tooltip", function() {
mouse('mouseover', 10, 10);
for(let i=10; i<200; i+= 10) {
setTimeout(
() => mouse('mousemove', i, 10),
i
);
}
this.clock.tick(200);
assert.isFalse(isOver);
});
});

View file

@ -0,0 +1,50 @@
'use strict';
// Here's a brief sketch of the class
// with things that you'll need anyway
class HoverIntent {
constructor({
sensitivity = 0.1, // speed less than 0.1px/ms means "hovering over an element"
interval = 100, // measure mouse speed once per 100ms: calculate the distance between previous and next points
elem,
over,
out
}) {
this.sensitivity = sensitivity;
this.interval = interval;
this.elem = elem;
this.over = over;
this.out = out;
// make sure "this" is the object in event handlers.
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseOver = this.onMouseOver.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);
// assign the handlers
elem.addEventListener("mouseover", this.onMouseOver);
elem.addEventListener("mouseout", this.onMouseOut);
// continue from this point
}
onMouseOver(event) {
/* ... */
}
onMouseOut(event) {
/* ... */
}
onMouseMove(event) {
/* ... */
}
destroy() {
/* your code to "disable" the functionality, remove all handlers */
};
}

View file

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="style.css">
<script src="hoverIntent.js"></script>
<script src="https://js.cx/test/libs.js"></script>
<script src="test.js"></script>
</head>
<body>
<div id="elem" class="clock">
<span class="hours">12</span> :
<span class="minutes">30</span> :
<span class="seconds">00</span>
</div>
<div id="tooltip" hidden>Tooltip</div>
<script>
// for the demo
setTimeout(function() {
new HoverIntent({
elem,
over() {
tooltip.hidden = false;
},
out() {
tooltip.hidden = true;
}
});
}, 2000);
</script>
</body>
</html>

View file

@ -0,0 +1,28 @@
.hours {
color: red;
}
.minutes {
color: green;
}
.seconds {
color: blue;
}
.clock {
border: 1px dashed black;
padding: 5px;
display: inline-block;
background: yellow;
position: absolute;
left: 0;
top: 0;
}
.tooltip {
position: absolute;
background: #eee;
border: 1px brown solid;
padding: 3px;
}

View file

@ -0,0 +1,98 @@
'use strict';
describe("hoverIntent", function() {
function mouse(eventType, x, y, options) {
let eventOptions = Object.assign({
bubbles: true,
clientX: x,
clientY: y,
pageX: x,
pageY: y,
target: elem
}, options || {});
elem.dispatchEvent(new MouseEvent(eventType, eventOptions));
}
let isOver;
let hoverIntent;
before(function() {
this.clock = sinon.useFakeTimers();
});
after(function() {
this.clock.restore();
});
beforeEach(function() {
isOver = false;
hoverIntent = new HoverIntent({
elem: elem,
over: function() {
isOver = true;
},
out: function() {
isOver = false;
}
});
})
afterEach(function() {
if (hoverIntent) {
hoverIntent.destroy();
}
})
it("mouseover -> immediately no tooltip", function() {
mouse('mouseover', 10, 10);
assert.isFalse(isOver);
});
it("mouseover -> pause shows tooltip", function() {
mouse('mouseover', 10, 10);
this.clock.tick(100);
assert.isTrue(isOver);
});
it("mouseover -> fast mouseout no tooltip", function() {
mouse('mouseover', 10, 10);
setTimeout(
() => mouse('mouseout', 300, 300, { relatedTarget: document.body}),
30
);
this.clock.tick(100);
assert.isFalse(isOver);
});
it("mouseover -> slow move -> tooltips", function() {
mouse('mouseover', 10, 10);
for(let i=10; i<200; i+= 10) {
setTimeout(
() => mouse('mousemove', i/5, 10),
i
);
}
this.clock.tick(200);
assert.isTrue(isOver);
});
it("mouseover -> fast move -> no tooltip", function() {
mouse('mouseover', 10, 10);
for(let i=10; i<200; i+= 10) {
setTimeout(
() => mouse('mousemove', i, 10),
i
);
}
this.clock.tick(200);
assert.isFalse(isOver);
});
});

View file

@ -0,0 +1,47 @@
importance: 5
---
# "Smart" tooltip
Write a function that shows a tooltip over an element only if the visitor moves the mouse *over it*, but not *through it*.
In other words, if the visitor moves the mouse on the element and stopped -- show the tooltip. And if he just moved the mouse through fast, then no need, who wants extra blinking?
Technically, we can measure the mouse speed over the element, and if it's slow then we assume that it comes "over the element" and show the tooltip, if it's fast -- then we ignore it.
Make a universal object `new HoverIntent(options)` for it. With `options`:
- `elem` -- element to track.
- `over` -- a function to call if the mouse is slowly moving the element.
- `out` -- a function to call when the mouse leaves the element (if `over` was called).
An example of using such object for the tooltip:
```js
// a sample tooltip
let tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Tooltip";
// the object will track mouse and call over/out
new HoverIntent({
elem,
over() {
tooltip.style.left = elem.getBoundingClientRect().left + 'px';
tooltip.style.top = elem.getBoundingClientRect().bottom + 5 + 'px';
document.body.append(tooltip);
},
out() {
tooltip.remove();
}
});
```
The demo:
[iframe src="solution" height=140]
If you move the mouse over the "clock" fast then nothing happens, and if you do it slow or stop on them, then there will be a tooltip.
Please note: the tooltip doesn't "blink" when the cursor between the clock subelements.

View file

@ -0,0 +1,186 @@
# Moving: mouseover/out, mouseenter/leave
Let's dive into more details about events that happen when mouse moves between elements.
[cut]
## Mouseover/mouseout, relatedTarget
The `mouseover` event occurs when a mouse pointer comes over an element, and `mouseout` -- when it leaves.
![](mouseover-mouseout.png)
These events are special, because they have a `relatedTarget`.
For `mouseover`:
- `event.target` -- is the element where the mouse came over.
- `event.relatedTarget` -- is the element from which the mouse came.
For `mouseout` the reverse:
- `event.target` -- is the element that mouse left.
- `event.relatedTarget` -- is the new under-the-pointer element (that mouse left for).
```online
In the example below each face feature is an element. When you move the mouse, you can see mouse events in the text area.
Each event has the information about where the element came and where it came from.
[codetabs src="mouseoverout" height=280]
```
```warn header="`relatedTarget` can be `null`"
The `relatedTarget` property can be `null`.
That's normal and just means that the mouse came not from another element, but from out of the window. Or that it left the window.
We should keep that possibility in mind when using `event.relatedTarget` in our code. If we access `event.relatedTarget.tagName`, then there will be an error.
```
## Events frequency
The `mousemove` event triggers when the mouse moves. But that doesn't mean that every pixel leads to an event.
The browser checks the mouse position from time to time. And if he notices changes then triggers the events.
That means that if the visitor is moving the mouse very fast then DOM-elements may be skipped:
![](mouseover-mouseout-over-elems.png)
If the mouse moves very fast from `#FROM` to `#TO` elements as painted above, then intermediate `<div>` (or some of them) may be skipped. The `mouseout` event may trigger on `#FROM` and then immediately `mouseover` on `#TO`.
In practice that's helpful, because if there may be many intermediate elements. We don't really want to process in and out of each one.
From the other side, we should keep in mind that we can't assume that the mouse slowly moves from one event to another. No, it can "jump".
In particular it's possible that the cursor jumps right inside the middle of the page from out of the window. And `relatedTarget=null`, because it came from "nowhere":
![](mouseover-mouseout-from-outside.png)
<div style="display:none">
In case of a fast move, intermediate elements may trigger no events. But if the mouse enters the element (`mouseover`), when we're guaranteed to have `mouseout` when it leaves it.
</div>
```online
Check it out "live" on a teststand below.
The HTML is two nested `<div>` elements. If you move the mouse fast over them, then there may be no events at all, or maybe only the red div triggers events, or maybe the green one.
Also try to move the pointer over the red `div`, and then move it out quickly down through the green one. If the movement is fast enough then the parent element is ignored.
[codetabs height=360 src="mouseoverout-fast"]
```
## "Extra" mouseout when leaving for a child
Imagine -- a mouse pointer entered an element. The `mouseover` triggered. Then the cursor goes into a child element. The interesting fact is that `mouseout` triggers in that case. The cursor is still in the element, but we have a `mouseout` from it!
![](mouseover-to-child.png)
That seems strange, but can be easily explained.
**According to the browser logic, the mouse cursor may be only over a *single* element at any time -- the most nested one (and top by z-index).**
So if it goes to another element (even a descendant), then it leaves the previous one. That simple.
There's a funny consequence that we can see on the example below.
The red `<div>` is nested inside the blue one. The blue `<div>` has `mouseover/out` handlers that log all events in the textarea below.
Try entering the blue element and then moving the mouse on the red one -- and watch the events:
[codetabs height=360 src="mouseoverout-child"]
1. On entering the blue one -- we get `mouseover [target: blue]`.
2. Then after moving from the blue to the red one -- we get `mouseout [target: blue]` (left the parent).
3. ...And immediately `mouseover [target: red]`.
So, for a handler that does not take `target` into account, it looks like we left the parent in `mouseout` in `(2)` and returned back to it by `mouseover` in `(3)`.
If we perform some actions on entering/leaving the element, then we'll get a lot of extra "false" runs. For simple stuff may be unnoticeable. For complex things that may bring unwanted side-effects.
We can fix it by using `mouseenter/mouseleave` events instead.
## Events mouseenter and mouseleave
Events `mouseenter/mouseleave` are like `mouseover/mouseout`. They also trigger when the mouse pointer enters/leaves the element.
But there are two differences:
1. Transitions inside the element are not counted.
2. Events `mouseenter/mouseleave` do not bubble.
These events are intuitively very clear.
When the pointer enters an element -- the `mouseenter` triggers, and then doesn't matter where it goes while inside the element. The `mouseleave` event only triggers when the cursor leaves it.
If we make the same example, but put `mouseenter/mouseleave` on the blue `<div>`, and do the same -- we can see that events trigger only on entering and leaving the blue `<div>`. No extra events when going to the red one and back. Children are ignored.
[codetabs height=340 src="mouseleave"]
## Event delegation
Events `mouseenter/leave` are very simple and easy to use. But they do not bubble. So we can't use event delegation with them.
Imagine we want to handle mouse enter/leave for table cells. And there are hundreds of cells.
The natural solution would be -- to set the handler on `<table>` and process events there. But `mouseenter/leave` don't bubble. So if such event happens on `<td>`, then only a handler on that `<td>` can catch it.
Handlers for `mouseenter/leave` on `<table>` only trigger on entering/leaving the whole table. It's impossible to get any information about transitions inside it.
Not a problem -- let's use `mouseover/mouseout`.
A simple handler may look like this:
```js
// let's highlight cells under mouse
table.onmouseover = function(event) {
let target = event.target;
target.style.background = 'pink';
};
table.onmouseout = function(event) {
let target = event.target;
target.style.background = '';
};
```
```online
[codetabs height=480 src="mouseenter-mouseleave-delegation"]
```
These handlers work when going from any element to any inside the table.
But we'd like to handle only transitions in and out of `<td>` as a whole. And highlight the cells as a whole. We don't want to handle transitions that happen between the children of `<td>`.
One of solutions:
- Remember the currently highlighted `<td>` in a variable.
- On `mouseover` -- ignore the event if we're still inside the current `<td>`.
- On `mouseout` -- ignore if we didn't leave the current `<td>`.
That filters out "extra" events when we are moving between the children of `<td>`.
```offline
The details are in the [full example](sandbox:mouseenter-mouseleave-delegation-2).
```
```online
Here's the full example with all details:
[codetabs height=380 src="mouseenter-mouseleave-delegation-2"]
Try to move the cursor in and out of table cells and inside them. Fast or slow -- doesn't better. Only `<td>` as a whole is highlighted unlike the example before.
```
## Итого
У `mouseover, mousemove, mouseout` есть следующие особенности:
- При быстром движении мыши события `mouseover, mousemove, mouseout` могут пропускать промежуточные элементы.
- События `mouseover` и `mouseout` -- единственные, у которых есть вторая цель: `relatedTarget` (`toElement/fromElement` в IE).
- События `mouseover/mouseout` подразумевают, что курсор находится над одним, самым глубоким элементом. Они срабатывают при переходе с родительского элемента на дочерний.
События `mouseenter/mouseleave` не всплывают и не учитывают переходы внутри элемента.

View file

@ -0,0 +1,74 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<table id="table">
<tr>
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
</tr>
<tr>
<td class="nw"><strong>Northwest</strong>
<br>Metal
<br>Silver
<br>Elders
</td>
<td class="n"><strong>North</strong>
<br>Water
<br>Blue
<br>Change
</td>
<td class="ne"><strong>Northeast</strong>
<br>Earth
<br>Yellow
<br>Direction
</td>
</tr>
<tr>
<td class="w"><strong>West</strong>
<br>Metal
<br>Gold
<br>Youth
</td>
<td class="c"><strong>Center</strong>
<br>All
<br>Purple
<br>Harmony
</td>
<td class="e"><strong>East</strong>
<br>Wood
<br>Blue
<br>Future
</td>
</tr>
<tr>
<td class="sw"><strong>Southwest</strong>
<br>Earth
<br>Brown
<br>Tranquility
</td>
<td class="s"><strong>South</strong>
<br>Fire
<br>Orange
<br>Fame
</td>
<td class="se"><strong>Southeast</strong>
<br>Wood
<br>Green
<br>Romance
</td>
</tr>
</table>
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,46 @@
// элемент TD, внутри которого сейчас курсор
var currentElem = null;
table.onmouseover = function(event) {
if (currentElem) {
// перед тем, как зайти в новый элемент, курсор всегда выходит из предыдущего
//
// если мы еще не вышли, значит это переход внутри элемента, отфильтруем его
return;
}
// посмотрим, куда пришёл курсор
var target = event.target;
// уж не на TD ли?
while (target != this) {
if (target.tagName == 'TD') break;
target = target.parentNode;
}
if (target == this) return;
// да, элемент перешёл внутрь TD!
currentElem = target;
target.style.background = 'pink';
};
table.onmouseout = function(event) {
// если курсор и так снаружи - игнорируем это событие
if (!currentElem) return;
// произошёл уход с элемента - проверим, куда, может быть на потомка?
var relatedTarget = event.relatedTarget;
if (relatedTarget) { // может быть relatedTarget = null
while (relatedTarget) {
// идём по цепочке родителей и проверяем,
// если переход внутрь currentElem - игнорируем это событие
if (relatedTarget == currentElem) return;
relatedTarget = relatedTarget.parentNode;
}
}
// произошло событие mouseout, курсор ушёл
currentElem.style.background = '';
currentElem = null;
};

View file

@ -0,0 +1,65 @@
#text {
display: block;
height: 100px;
width: 456px;
}
#table th {
text-align: center;
font-weight: bold;
}
#table td {
width: 150px;
white-space: nowrap;
text-align: center;
vertical-align: bottom;
padding-top: 5px;
padding-bottom: 12px;
}
#table .nw {
background: #999;
}
#table .n {
background: #03f;
color: #fff;
}
#table .ne {
background: #ff6;
}
#table .w {
background: #ff0;
}
#table .c {
background: #60c;
color: #fff;
}
#table .e {
background: #09f;
color: #fff;
}
#table .sw {
background: #963;
color: #fff;
}
#table .s {
background: #f60;
color: #fff;
}
#table .se {
background: #0c3;
color: #fff;
}
#table .highlight {
background: red;
}

View file

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<table id="table">
<tr>
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
</tr>
<tr>
<td class="nw"><strong>Northwest</strong>
<br>Metal
<br>Silver
<br>Elders
</td>
<td class="n"><strong>North</strong>
<br>Water
<br>Blue
<br>Change
</td>
<td class="ne"><strong>Northeast</strong>
<br>Earth
<br>Yellow
<br>Direction
</td>
</tr>
<tr>
<td class="w"><strong>West</strong>
<br>Metal
<br>Gold
<br>Youth
</td>
<td class="c"><strong>Center</strong>
<br>All
<br>Purple
<br>Harmony
</td>
<td class="e"><strong>East</strong>
<br>Wood
<br>Blue
<br>Future
</td>
</tr>
<tr>
<td class="sw"><strong>Southwest</strong>
<br>Earth
<br>Brown
<br>Tranquility
</td>
<td class="s"><strong>South</strong>
<br>Fire
<br>Orange
<br>Fame
</td>
<td class="se"><strong>Southeast</strong>
<br>Wood
<br>Green
<br>Romance
</td>
</tr>
</table>
<textarea id="text"></textarea>
<input type="button" onclick="text.value=''" value="Clear">
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,13 @@
table.onmouseover = function(event) {
var target = event.target;
target.style.background = 'pink';
text.value += "mouseover " + target.tagName + "\n";
text.scrollTop = text.scrollHeight;
};
table.onmouseout = function(event) {
var target = event.target;
target.style.background = '';
text.value += "mouseout " + target.tagName + "\n";
text.scrollTop = text.scrollHeight;
};

View file

@ -0,0 +1,65 @@
#text {
display: block;
height: 100px;
width: 456px;
}
#table th {
text-align: center;
font-weight: bold;
}
#table td {
width: 150px;
white-space: nowrap;
text-align: center;
vertical-align: bottom;
padding-top: 5px;
padding-bottom: 12px;
}
#table .nw {
background: #999;
}
#table .n {
background: #03f;
color: #fff;
}
#table .ne {
background: #ff6;
}
#table .w {
background: #ff0;
}
#table .c {
background: #60c;
color: #fff;
}
#table .e {
background: #09f;
color: #fff;
}
#table .sw {
background: #963;
color: #fff;
}
#table .s {
background: #f60;
color: #fff;
}
#table .se {
background: #0c3;
color: #fff;
}
#table .highlight {
background: red;
}

View file

@ -0,0 +1,79 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<table id="table">
<tr>
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
</tr>
<tr>
<td class="nw"><strong>Northwest</strong>
<br>Metal
<br>Silver
<br>Elders
</td>
<td class="n"><strong>North</strong>
<br>Water
<br>Blue
<br>Change
</td>
<td class="ne"><strong>Northeast</strong>
<br>Earth
<br>Yellow
<br>Direction
</td>
</tr>
<tr>
<td class="w"><strong>West</strong>
<br>Metal
<br>Gold
<br>Youth
</td>
<td class="c"><strong>Center</strong>
<br>All
<br>Purple
<br>Harmony
</td>
<td class="e"><strong>East</strong>
<br>Wood
<br>Blue
<br>Future
</td>
</tr>
<tr>
<td class="sw"><strong>Southwest</strong>
<br>Earth
<br>Brown
<br>Tranquility
</td>
<td class="s"><strong>South</strong>
<br>Fire
<br>Orange
<br>Fame
</td>
<td class="se"><strong>Southeast</strong>
<br>Wood
<br>Green
<br>Romance
</td>
</tr>
</table>
<textarea id="text"></textarea>
<input type="button" onclick="text.value=''" value="Очистить">
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,6 @@
table.onmouseenter = table.onmouseleave = log;
function log(event) {
text.value += event.type + ' [target: ' + event.target.tagName + ']\n';
text.scrollTop = text.scrollHeight;
}

View file

@ -0,0 +1,65 @@
#text {
display: block;
height: 100px;
width: 456px;
}
#table th {
text-align: center;
font-weight: bold;
}
#table td {
width: 150px;
white-space: nowrap;
text-align: center;
vertical-align: bottom;
padding-top: 5px;
padding-bottom: 12px;
}
#table .nw {
background: #999;
}
#table .n {
background: #03f;
color: #fff;
}
#table .ne {
background: #ff6;
}
#table .w {
background: #ff0;
}
#table .c {
background: #60c;
color: #fff;
}
#table .e {
background: #09f;
color: #fff;
}
#table .sw {
background: #963;
color: #fff;
}
#table .s {
background: #f60;
color: #fff;
}
#table .se {
background: #0c3;
color: #fff;
}
#table .highlight {
background: red;
}

View file

@ -0,0 +1,22 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="blue" onmouseenter="log(event)" onmouseleave="log(event)">
<div id="red"></div>
</div>
<textarea id="text"></textarea>
<input type="button" onclick="text.value=''" value="Clear">
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,4 @@
function log(event) {
text.value += event.type + ' [target: ' + event.target.id + ']\n';
text.scrollTop = text.scrollHeight;
}

View file

@ -0,0 +1,21 @@
#blue {
background: blue;
width: 160px;
height: 160px;
position: relative;
}
#red {
background: red;
width: 70px;
height: 70px;
position: absolute;
left: 45px;
top: 45px;
}
#text {
display: block;
height: 100px;
width: 400px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,22 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="blue" onmouseover="mouselog(event)" onmouseout="mouselog(event)">
<div class="red"></div>
</div>
<textarea id="text"></textarea>
<input type="button" onclick="text.value=''" value="Clear">
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,4 @@
function mouselog(event) {
text.value += event.type + ' [target: ' + event.target.className + ']\n'
text.scrollTop = text.scrollHeight
}

View file

@ -0,0 +1,21 @@
.blue {
background: blue;
width: 160px;
height: 160px;
position: relative;
}
.red {
background: red;
width: 100px;
height: 100px;
position: absolute;
left: 30px;
top: 30px;
}
textarea {
height: 100px;
width: 400px;
display: block;
}

View file

@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="green">
<div id="red">Test</div>
</div>
<input onclick="clearText()" value="Clear" type="button">
<textarea id="text"></textarea>
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,47 @@
green.onmouseover = green.onmouseout = green.onmousemove = handler;
function handler(event) {
var type = event.type;
while (type < 11) type += ' ';
log(type + " target=" + event.target.id)
return false;
}
function clearText() {
text.value = "";
lastMessage = "";
}
var lastMessageTime = 0;
var lastMessage = "";
var repeatCounter = 1;
function log(message) {
if (lastMessageTime == 0) lastMessageTime = new Date();
var time = new Date();
if (time - lastMessageTime > 500) {
message = '------------------------------\n' + message;
}
if (message === lastMessage) {
repeatCounter++;
if (repeatCounter == 2) {
text.value = text.value.trim() + ' x 2\n';
} else {
text.value = text.value.slice(0, text.value.lastIndexOf('x') + 1) + repeatCounter + "\n";
}
} else {
repeatCounter = 1;
text.value += message + "\n";
}
text.scrollTop = text.scrollHeight;
lastMessageTime = time;
lastMessage = message;
}

View file

@ -0,0 +1,23 @@
#green {
height: 50px;
width: 160px;
background: green;
}
#red {
height: 20px;
width: 110px;
background: red;
color: white;
font-weight: bold;
padding: 5px;
text-align: center;
margin: 20px;
}
#text {
font-size: 12px;
height: 200px;
width: 360px;
display: block;
}

View file

@ -0,0 +1,37 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="container">
<div class="smiley-green">
<div class="left-eye"></div>
<div class="right-eye"></div>
<div class="smile"></div>
</div>
<div class="smiley-yellow">
<div class="left-eye"></div>
<div class="right-eye"></div>
<div class="smile"></div>
</div>
<div class="smiley-red">
<div class="left-eye"></div>
<div class="right-eye"></div>
<div class="smile"></div>
</div>
</div>
<textarea id="log">Events will show up here!
</textarea>
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,21 @@
container.onmouseover = container.onmouseout = handler;
function handler(event) {
function str(el) {
if (!el) return "null"
return el.className || el.tagName;
}
log.value += event.type + ': ' +
'target=' + str(event.target) +
', relatedTarget=' + str(event.relatedTarget) + "\n";
log.scrollTop = log.scrollHeight;
if (event.type == 'mouseover') {
event.target.style.background = 'pink'
}
if (event.type == 'mouseout') {
event.target.style.background = ''
}
}

View file

@ -0,0 +1,179 @@
body,
html {
margin: 0;
padding: 0;
}
#container {
border: 1px solid brown;
padding: 10px;
width: 330px;
margin-bottom: 5px;
box-sizing: border-box;
}
#log {
height: 120px;
width: 350px;
display: block;
box-sizing: border-box;
}
[class^="smiley-"] {
display: inline-block;
width: 70px;
height: 70px;
border-radius: 50%;
margin-right: 20px;
}
.smiley-green {
background: #a9db7a;
border: 5px solid #92c563;
position: relative;
}
.smiley-green .left-eye {
width: 18%;
height: 18%;
background: #84b458;
position: relative;
top: 29%;
left: 22%;
border-radius: 50%;
float: left;
}
.smiley-green .right-eye {
width: 18%;
height: 18%;
border-radius: 50%;
position: relative;
background: #84b458;
top: 29%;
right: 22%;
float: right;
}
.smiley-green .smile {
position: absolute;
top: 67%;
left: 16.5%;
width: 70%;
height: 20%;
overflow: hidden;
}
.smiley-green .smile:after,
.smiley-green .smile:before {
content: "";
position: absolute;
top: -50%;
left: 0%;
border-radius: 50%;
background: #84b458;
height: 100%;
width: 97%;
}
.smiley-green .smile:after {
background: #84b458;
height: 80%;
top: -40%;
left: 0%;
}
.smiley-yellow {
background: #eed16a;
border: 5px solid #dbae51;
position: relative;
}
.smiley-yellow .left-eye {
width: 18%;
height: 18%;
background: #dba652;
position: relative;
top: 29%;
left: 22%;
border-radius: 50%;
float: left;
}
.smiley-yellow .right-eye {
width: 18%;
height: 18%;
border-radius: 50%;
position: relative;
background: #dba652;
top: 29%;
right: 22%;
float: right;
}
.smiley-yellow .smile {
position: absolute;
top: 67%;
left: 19%;
width: 65%;
height: 14%;
background: #dba652;
overflow: hidden;
border-radius: 8px;
}
.smiley-red {
background: #ee9295;
border: 5px solid #e27378;
position: relative;
}
.smiley-red .left-eye {
width: 18%;
height: 18%;
background: #d96065;
position: relative;
top: 29%;
left: 22%;
border-radius: 50%;
float: left;
}
.smiley-red .right-eye {
width: 18%;
height: 18%;
border-radius: 50%;
position: relative;
background: #d96065;
top: 29%;
right: 22%;
float: right;
}
.smiley-red .smile {
position: absolute;
top: 57%;
left: 16.5%;
width: 70%;
height: 20%;
overflow: hidden;
}
.smiley-red .smile:after,
.smiley-red .smile:before {
content: "";
position: absolute;
top: 50%;
left: 0%;
border-radius: 50%;
background: #d96065;
height: 100%;
width: 97%;
}
.smiley-red .smile:after {
background: #d96065;
height: 80%;
top: 60%;
left: 0%;
}