Hey , you !


  • Home

  • Archives

WheelEvent Compatibility and Delta Handler

Posted on 2019-01-27 | In js

I have seen many times people talking about the compatibility problems of wheelEvent, something about wheel, DOMMouseScroll, mousewheel etc. But now, it seems we don’t need to care about those compatibility problems anymore.

As mdn shows, IE9 support wheel event in addEventListener and DOMMouseScroll is only needed for Firefox 17-. And for other major browsers, they all support wheel event.

But, there are other problems still need to take care.

delta

event.delta returns different numbers in the major browsers. For instance, if you run the code below at your console in some page,

document.body.addEventListener('wheel', function(event) {
console.log(event.deltaY)
})

then moving your mouse wheel, you will see the deltaY numbers.

browsers/win7 deltaY
Chrome 70 100
Firefox 64 3
IE 11 88.19

Well, you can find a solution to unify the deltaY. But I think the easier way is to give up unifying and only do the accumulation job. For example, try the code below:

let wheelSize = 0
document.body.addEventListener('wheel', function(event) {
wheelSize += event.deltaY > 0 ? 0.025 : -0.025
console.log(wheelSize, event.deltaY)
})

In this way, we will get the unified wheelSize.

More Situations Need to Watch Out

When used in projects, there are more things we need to watch out. For example,

  • We might have wheelSize limit.
  • We might want to prevent calculating wheelSize when the page has been scrolled to the top or the bottom.
  • …

And the code would become more complicated. For instance,

let targetNode = document.body
let curWheelSize = 1
let minWheelSize = 1
let maxWheelSize = 10
let minScrollTop = 0
let maxScrollTop =
targetNode.parentNode.scrollHeight - targetNode.parentNode.offsetHeight
targetNode.addEventListener('wheel', wheelCb)
function wheelCb(event) {
const isDeltaYPositive = event.deltaY > 0
let scrollTop = targetNode.parentNode.scrollTop
if (
(scrollTop <= minScrollTop && !isDeltaYPositive) ||
(scrollTop >= maxScrollTop && isDeltaYPositive)
) {
return
}
let stepSize = isDeltaYPositive ? 0.025 : -0.025
let targetWheelSize = curWheelSize + stepSize
targetWheelSize = Math.max(targetWheelSize, minWheelSize)
targetWheelSize = Math.min(targetWheelSize, maxWheelSize)
curWheelSize = targetWheelSize
}

Original Post

Reference

Tricks in Vue

Posted on 2019-01-25 | In vue

There are some tricks which can’t be found easily in Vue.js homepage. So, for convenient, I summarized them here.

Vue

Access Global Variable in Template

Have you ever done something like this in lots of components?

<template>
<a @click="window.open('...')"></a>
</template>

<script>
export default {
name: "App",
data() {
return {
window: window
};
}
};
</script>

or

<template>
<a @click="bus.$emit('some-event')"></a>
</template>

<script>
export default {
name: "App",
data() {
return {
bus: bus
};
}
};
</script>

Actually, you don’t have to, you can register window or bus in Vue.prototype like:

Vue.prototype.window = window;
Vue.prototype.bus = bus;

in the main.js or the entry file. Then you can use bus or window in template directly. Also, this usage prevents Vue watching the attributes of bus or window which would bring a waste of performance.

Reactive or Not Reactive

Always, if we want a data reactive, we have to do something like this:

data: {
newTodoText: '',
visitCount: 0,
hideCompletedTodos: false,
todos: [],
error: null
}

Set some initial value to adds all the properties found in its data object to Vue’s reactivity system.

Things we need to take care about is:

  • If we want to add reactive attributes dynamically, we have to use something like Vue.set or this.$set. Otherwise, they might not be reactive.
  • If we definitely don’t want some data to participate in Vue’s reactivity system even we initialize it in data. We can use something like Object.freeze(). For example, freeze a huge array to improve performance.

Scoped Style Won’t Work on Dynamically Inserted Elements

I always use the <style scoped> tag in .vue files. It is always good except when we want to insert elements dynamically. For example:

<template>
<div id="app" ref="app"></div>
</template>

<script>
export default {
name: "App",
mounted() {
this.$refs.app.innerHTML = `<h1 class="App__title">App__title</h1>`;
}
};
</script>

<style scoped>
.App__title {
color: red;
}
</style>

color: red won’t work on .App__title because of scoped. The actual style is rendered with a unique attribute like:

So, how do we solve this? /deep/ or >>>.

<style scoped>
/deep/ .App__title {
color: red;
}
</style>

They can be used to override child component style. Here is the doc.

Smarter Watchers

Have you ever written code like this:

{
// ...
created() {
this.fetchPostList()
},
watch: {
searchInputValue() {
this.fetchPostList()
}
}
// ...
}

Actually, you can simplify it by

{
// ...
watch: {
searchInputValue:{
handler: 'fetchPostList',
immediate: true
}
}
// ...
}

As the doc said:

Passing in immediate: true in the option will trigger the callback immediately with the current value of the expression.

$attrs and $listeners

I don’t know if you have used $attrs and $listeners from this. However, I never used those until I met this situation. For example:

<div id="app">
<base-input
:value="value"
placeholder="parentPlaceholder"
otherAttrs="otherAttrs"
@input="inputCb"
@click="clickCb"
></base-input>
</div>
<script>
let BaseInput = {
name: "base-input",
template: `
<div>
<input type="text" :value="value" :placeholder="placeholder" :otherAttrs="otherAttrs" @input="$emit('input',$event)" @click="$emit('click',$event)" @focus="focusCb" />
</div>`,
props: {
value: {
type: String
}
},
computed: {
listeners() {
const listeners = {
...this.$listeners,
focus: this.focusCb
};
return listeners;
}
},
methods: {
focusCb(event) {
console.log("child", event);
}
}
};
window.app = new Vue({
el: "#app",
components: {
BaseInput
},
data: {
value: "",
parentPlaceholder: "parentPlaceholder"
},
methods: {
inputCb(event) {
console.log(event);
},
clickCb(event) {
console.log(event);
}
}
});
</script>

It’s obviously tedious to bind every attribute and listener by hand. Actually, this is where $attrs and $listeners will help us. We can write the BaseInput template like:

let BaseInput = {
name: "base-input",
template: `<div><input type="text" :value="value" v-bind="$attrs" v-on="listeners" /></div>`,
props: {
value: {
type: String
}
},
computed: {
listeners() {
const listeners = {
...this.$listeners,
// move `focus` in to `listeners` instead of adding one more `focus` listener.
focus: this.focusCb
};
return listeners;
}
},
methods: {
focusCb(event) {
console.log("child", event);
}
}
};

Vue-Router

$router and $route

Have you ever wonder about the relationship between $router and $route? I give you a hint:

this.$router.currentRoute === this.$route; //true

Vuex

Commit Data by One Mutation

We can’t directly mutate state in Vuex. Instead, we have to commit a mutation to mutate the data. However, it would be tedious to write lots of similar mutations like this:

let store = new Vuex.Store({
modules: {
// ...
},
mutations: {
updateName(state, data) {
state.name = data;
},
updateChildrenCount(state, data) {
state.children.count = data;
}
// other similar mutations
}
});

We can write a public mutation to do this like:

let store = new Vuex.Store({
modules: {
// ...
},
mutations: {
replaceProperty(state, { path, data }) {
if (typeof path !== "string") {
return;
}
path = path.split(".");
let targetObj = path.slice(0, -1).reduce((re, key) => re[key], state);
targetObj[path.pop()] = data;
}
}
});

Then we can mutate state in anywhere with only one mutation like:

commit(
"replaceProperty",
{
path: "name",
data: name
},
{ root: true }
);
commit(
"replaceProperty",
{
path: "children.count",
data: data
},
{ root: true }
);
commit(
"replaceProperty",
{
path: "some.other.deep.path.in.state",
data: data
},
{ root: true }
);

It would also work for modules!

Original Post

Reference

  • 7 Secret Patterns Vue Consultants Don’t Want You to Know - Chris Fritz
  • vue-loader/issues/749

event.preventDefault() in contextmenu Would Trigger mouseup event

Posted on 2018-12-27 | In js

Question

How many logs would you have when you click and release right mouse button?

Demo1

document.oncontextmenu = function() {
console.error(event.type, Date.now())
}
document.onmouseup = function() {
console.error(event.type, Date.now())
}

Demo2

document.oncontextmenu = function() {
event.preventDefault()
console.error(event.type, Date.now())
}
document.onmouseup = function() {
console.error(event.type, Date.now())
}

The answer is 1,2. The question is

  • Does that matter?
  • And why?

Matters

Normally, click and release right button would only trigger contextmenu instead of mouseup.

document.oncontextmenu = function() {
console.error(event.type, Date.now())
}
document.onmouseup = function() {
console.error(event.type, Date.now())
}

It does make senses in most cases that when we were using onmouseup means that we only want to listen to the mouseup of left button.

However, if you used event.preventDefault() in contextmenu, mouseup event would be triggered when you press and release the right button.

document.oncontextmenu = function() {
event.preventDefault()
console.error(event.type, Date.now())
}
document.onmouseup = function() {
console.error(event.type, Date.now())
}

In most cases, this is not expected as it changes the behavior of mouseup listener because of changes in contextmenu listener. To be honest, this awful unless we add button detection in mouseup listener every time we want to do something when user only press and release left button.

document.oncontextmenu = function() {
event.preventDefault()
console.error(event.type, Date.now())
}
document.onmouseup = function() {
if (event.button !== 0) {
return
}
console.error(event.type, Date.now())
}

Why

according to mdn mouseup, default action of mouseup is

Default Action : Invoke a context menu (in combination with the right mouse button, if supported)

document.oncontextmenu = function() {
console.error(event.type, Date.now())
}
document.onmouseup = function() {
event.preventDefault()
console.error(event.type, Date.now())
}

However, at least Chrome 70 didn’t prevent triggering contextmenu event. But it might explain something.

  • Because default action of rightClick was to invoke a context menu. So, mouseup won’t get triggered.
  • And event.preventDefault() in contextmenu listener did the actual prevent job. So, mouseup
    get triggered.

Anyway, it’s still hard to understand.

Original Post

Reference

1…567…18

xianshenglu

54 posts
14 categories
142 tags
© 2023 xianshenglu
Powered by Hexo
|
Theme — NexT.Muse v5.1.4