WIP
This commit is contained in:
parent
718d9df37b
commit
b24b05d3ca
1436 changed files with 131 additions and 106126 deletions
18
1-js/02-b/1-b1/2-hoverintent/solution.md
Normal file
18
1-js/02-b/1-b1/2-hoverintent/solution.md
Normal 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. When we're going out of the element, and `over` was executed, run `out`.
|
||||
|
||||
But how to measure the speed?
|
||||
|
||||
The first idea can be: run a 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 for mouse events, like `mousemove`, and take coordinates from the event object.
|
||||
|
||||
So let's set a handler on `mousemove` to track coordinates and remember them. And then compare them, once per `100ms`.
|
||||
|
||||
P.S. Please note: the solution tests use `dispatchEvent` to see if the tooltip works right.
|
104
1-js/02-b/1-b1/2-hoverintent/solution/hoverIntent.js
Normal file
104
1-js/02-b/1-b1/2-hoverintent/solution/hoverIntent.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
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) {
|
||||
// if there was a stop over the element
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
52
1-js/02-b/1-b1/2-hoverintent/source/hoverIntent.js
Normal file
52
1-js/02-b/1-b1/2-hoverintent/source/hoverIntent.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
// 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) {
|
||||
console.error(console.log);
|
||||
|
||||
console.log("OVER", event);
|
||||
/* ... */
|
||||
}
|
||||
|
||||
onMouseOut(event) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
onMouseMove(event) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
|
||||
destroy() {
|
||||
/* your code to "disable" the functionality, remove all handlers */
|
||||
/* it's needed for the tests to work */
|
||||
}
|
||||
|
||||
}
|
24
1-js/02-b/1-b1/2-hoverintent/source/index.html
Normal file
24
1-js/02-b/1-b1/2-hoverintent/source/index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<link rel="stylesheet" href="style.css">
|
||||
<script src="hoverIntent.js"></script>
|
||||
|
||||
<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>
|
||||
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;
|
||||
}
|
||||
});
|
||||
</script>
|
38
1-js/02-b/1-b1/2-hoverintent/source/style.css
Normal file
38
1-js/02-b/1-b1/2-hoverintent/source/style.css
Normal file
|
@ -0,0 +1,38 @@
|
|||
.hours {
|
||||
color: red;
|
||||
}
|
||||
|
||||
body {
|
||||
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 sans-serif;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
z-index: 100000;
|
||||
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
|
||||
}
|
96
1-js/02-b/1-b1/2-hoverintent/source/test.js
Normal file
96
1-js/02-b/1-b1/2-hoverintent/source/test.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
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 -> when the pointer just arrived, no tooltip", function() {
|
||||
mouse('mouseover', 10, 10);
|
||||
assert.isFalse(isOver);
|
||||
});
|
||||
|
||||
it("mouseover -> after a delay, the tooltip shows up", function() {
|
||||
mouse('mouseover', 10, 10);
|
||||
this.clock.tick(100);
|
||||
assert.isTrue(isOver);
|
||||
});
|
||||
|
||||
it("mouseover -> followed by fast mouseout leads doesn't show 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);
|
||||
});
|
||||
|
||||
});
|
49
1-js/02-b/1-b1/2-hoverintent/task.md
Normal file
49
1-js/02-b/1-b1/2-hoverintent/task.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
importance: 5
|
||||
type: html
|
||||
|
||||
---
|
||||
|
||||
# "Smart" tooltip
|
||||
|
||||
Write a function that shows a tooltip over an element only if the visitor moves the mouse *to it*, but not *through it*.
|
||||
|
||||
In other words, if the visitor moves the mouse to the element and stops there -- show the tooltip. And if they just moved the mouse through, 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.
|
||||
|
||||
Its `options`:
|
||||
- `elem` -- element to track.
|
||||
- `over` -- a function to call if the mouse came to the element: that is, it moves slowly or stopped over it.
|
||||
- `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 moves between the clock subelements.
|
3
1-js/02-b/1-b1/article.md
Normal file
3
1-js/02-b/1-b1/article.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# An Introduction to JavaScript
|
||||
|
||||
Let's see what's so special about JavaScript, what we can achieve with it, and which other technologies play well with it.
|
3
1-js/02-b/index.md
Normal file
3
1-js/02-b/index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# An introduction
|
||||
|
||||
About the JavaScript language and the environment to develop with it.
|
Loading…
Add table
Add a link
Reference in a new issue