update ColorPicker
This commit is contained in:
parent
dab3947668
commit
e7893a68ed
12 changed files with 994 additions and 26 deletions
|
@ -1 +1,2 @@
|
||||||
src/directives
|
src/directives
|
||||||
|
src/utils/throttle.js
|
24
LICENSE
24
LICENSE
|
@ -151,4 +151,26 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 greyby
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div style="margin: 100px;">
|
<div style="margin: 100px;">
|
||||||
|
{{ color.rgba }}
|
||||||
<!--<Input placeholder="请输入..." size="large" style="width: 50px;"></Input>-->
|
<!--<Input placeholder="请输入..." size="large" style="width: 50px;"></Input>-->
|
||||||
<!--<color-picker placement="bottom-start" size="large"></color-picker>-->
|
<!--<color-picker placement="bottom-start" size="large"></color-picker>-->
|
||||||
<!--<Date-picker type="date" placeholder="选择日期" size="large" style="width: 200px"></Date-picker>-->
|
<!--<Date-picker type="date" placeholder="选择日期" size="large" style="width: 200px"></Date-picker>-->
|
||||||
<color-picker recommend alpha placement="bottom" size="default"></color-picker>
|
<color-picker v-model="color" alpha :recommend="true" placement="bottom" size="default"></color-picker>
|
||||||
|
<color-picker v-model="color" :alpha="false" :recommend="false" placement="bottom" size="default"></color-picker>
|
||||||
<!--<Date-picker type="date" placeholder="选择日期" style="width: 200px"></Date-picker>-->
|
<!--<Date-picker type="date" placeholder="选择日期" style="width: 200px"></Date-picker>-->
|
||||||
<!--<color-picker placement="bottom-start" size="small"></color-picker>-->
|
<!--<color-picker placement="bottom-start" size="small"></color-picker>-->
|
||||||
<!--<Date-picker type="date" placeholder="选择日期" size="small" style="width: 200px"></Date-picker>-->
|
<!--<Date-picker type="date" placeholder="选择日期" size="small" style="width: 200px"></Date-picker>-->
|
||||||
|
@ -13,7 +15,30 @@
|
||||||
export default {
|
export default {
|
||||||
props: {},
|
props: {},
|
||||||
data () {
|
data () {
|
||||||
return {};
|
return {
|
||||||
|
color: {
|
||||||
|
hex: '#194d33',
|
||||||
|
hsl: {
|
||||||
|
h: 150,
|
||||||
|
s: 0.5,
|
||||||
|
l: 0.2,
|
||||||
|
a: 1
|
||||||
|
},
|
||||||
|
hsv: {
|
||||||
|
h: 150,
|
||||||
|
s: 0.66,
|
||||||
|
v: 0.30,
|
||||||
|
a: 1
|
||||||
|
},
|
||||||
|
rgba: {
|
||||||
|
r: 25,
|
||||||
|
g: 77,
|
||||||
|
b: 51,
|
||||||
|
a: 1
|
||||||
|
},
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
methods: {}
|
methods: {}
|
||||||
|
|
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "iview",
|
"name": "iview",
|
||||||
"version": "2.0.0-rc.18",
|
"version": "2.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -15043,6 +15043,11 @@
|
||||||
"setimmediate": "1.0.5"
|
"setimmediate": "1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tinycolor2": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
|
||||||
|
},
|
||||||
"tmp": {
|
"tmp": {
|
||||||
"version": "0.0.31",
|
"version": "0.0.31",
|
||||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz",
|
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz",
|
||||||
|
|
|
@ -43,7 +43,8 @@
|
||||||
"async-validator": "^1.7.1",
|
"async-validator": "^1.7.1",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"deepmerge": "^1.5.0",
|
"deepmerge": "^1.5.0",
|
||||||
"popper.js": "^0.6.4"
|
"popper.js": "^0.6.4",
|
||||||
|
"tinycolor2": "^1.4.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^2.4.1"
|
"vue": "^2.4.1"
|
||||||
|
|
77
src/components/color-picker/alpha.vue
Normal file
77
src/components/color-picker/alpha.vue
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<div class="ivu-color-picker-alpha">
|
||||||
|
<div class="ivu-color-picker-alpha-checkboard-wrap">
|
||||||
|
<div class="ivu-color-picker-alpha-checkerboard"></div>
|
||||||
|
</div>
|
||||||
|
<div class="ivu-color-picker-alpha-gradient" :style="{background: gradientColor}"></div>
|
||||||
|
<div class="ivu-color-picker-alpha-container" ref="container"
|
||||||
|
@mousedown="handleMouseDown"
|
||||||
|
@touchmove="handleChange"
|
||||||
|
@touchstart="handleChange">
|
||||||
|
<div class="ivu-color-picker-alpha-pointer" :style="{left: colors.a * 100 + '%'}">
|
||||||
|
<div class="ivu-color-picker-alpha-picker"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Alpha',
|
||||||
|
props: {
|
||||||
|
value: Object,
|
||||||
|
onChange: Function
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
colors () {
|
||||||
|
return this.value;
|
||||||
|
},
|
||||||
|
gradientColor () {
|
||||||
|
const rgba = this.colors.rgba;
|
||||||
|
const rgbStr = [rgba.r, rgba.g, rgba.b].join(',');
|
||||||
|
return 'linear-gradient(to right, rgba(' + rgbStr + ', 0) 0%, rgba(' + rgbStr + ', 1) 100%)';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange (e, skip) {
|
||||||
|
!skip && e.preventDefault();
|
||||||
|
const container = this.$refs.container;
|
||||||
|
const containerWidth = container.clientWidth;
|
||||||
|
|
||||||
|
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||||
|
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||||
|
const left = pageX - xOffset;
|
||||||
|
|
||||||
|
let a;
|
||||||
|
if (left < 0) {
|
||||||
|
a = 0;
|
||||||
|
} else if (left > containerWidth) {
|
||||||
|
a = 1;
|
||||||
|
} else {
|
||||||
|
a = Math.round(left * 100 / containerWidth) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.colors.a !== a) {
|
||||||
|
this.$emit('change', {
|
||||||
|
h: this.colors.hsl.h,
|
||||||
|
s: this.colors.hsl.s,
|
||||||
|
l: this.colors.hsl.l,
|
||||||
|
a: a,
|
||||||
|
source: 'rgba'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMouseDown (e) {
|
||||||
|
this.handleChange(e, true);
|
||||||
|
window.addEventListener('mousemove', this.handleChange);
|
||||||
|
window.addEventListener('mouseup', this.handleMouseUp);
|
||||||
|
},
|
||||||
|
handleMouseUp () {
|
||||||
|
this.unbindEventListeners();
|
||||||
|
},
|
||||||
|
unbindEventListeners () {
|
||||||
|
window.removeEventListener('mousemove', this.handleChange);
|
||||||
|
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -8,12 +8,14 @@
|
||||||
</div>
|
</div>
|
||||||
<Dropdown-menu slot="list">
|
<Dropdown-menu slot="list">
|
||||||
<div :class="[prefixCls + '-picker']">
|
<div :class="[prefixCls + '-picker']">
|
||||||
<div :class="[prefixCls + '-picker-panel']"></div>
|
<div :class="[prefixCls + '-picker-panel']">
|
||||||
|
<Saturation v-model="saturationColors" @change="childChange"></Saturation>
|
||||||
|
</div>
|
||||||
<div :class="[prefixCls + '-picker-hue-slider']">
|
<div :class="[prefixCls + '-picker-hue-slider']">
|
||||||
<Slider v-model="hueNumber" :min="0" :max="255"></Slider>
|
<Hue v-model="saturationColors" @change="childChange"></Hue>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="alpha" :class="[prefixCls + '-picker-alpha-slider']">
|
<div v-if="alpha" :class="[prefixCls + '-picker-alpha-slider']">
|
||||||
<Slider v-model="alphaNumber" :min="0" :max="100"></Slider>
|
<Alpha v-model="saturationColors" @change="childChange"></Alpha>
|
||||||
</div>
|
</div>
|
||||||
<recommend-colors v-if="colors.length" :list="colors" :class="[prefixCls + '-picker-colors']"></recommend-colors>
|
<recommend-colors v-if="colors.length" :list="colors" :class="[prefixCls + '-picker-colors']"></recommend-colors>
|
||||||
<recommend-colors v-if="!colors.length && recommend" :list="recommendedColor" :class="[prefixCls + '-picker-colors']"></recommend-colors>
|
<recommend-colors v-if="!colors.length && recommend" :list="recommendedColor" :class="[prefixCls + '-picker-colors']"></recommend-colors>
|
||||||
|
@ -23,22 +25,76 @@
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
import Dropdown from '../dropdown/dropdown.vue';
|
import Dropdown from '../dropdown/dropdown.vue';
|
||||||
import DropdownMenu from '../dropdown/dropdown-menu.vue';
|
import DropdownMenu from '../dropdown/dropdown-menu.vue';
|
||||||
import Slider from '../slider/slider.vue';
|
|
||||||
import RecommendColors from './recommend-colors.vue';
|
import RecommendColors from './recommend-colors.vue';
|
||||||
import Confirm from '../date-picker/base/confirm.vue';
|
import Confirm from '../date-picker/base/confirm.vue';
|
||||||
|
|
||||||
|
import Saturation from './saturation.vue';
|
||||||
|
import Hue from './hue.vue';
|
||||||
|
import Alpha from './alpha.vue';
|
||||||
|
|
||||||
import { oneOf } from '../../utils/assist';
|
import { oneOf } from '../../utils/assist';
|
||||||
|
|
||||||
const prefixCls = 'ivu-color-picker';
|
const prefixCls = 'ivu-color-picker';
|
||||||
const inputPrefixCls = 'ivu-input';
|
const inputPrefixCls = 'ivu-input';
|
||||||
|
|
||||||
|
function _colorChange (data, oldHue) {
|
||||||
|
const alpha = data && data.a;
|
||||||
|
let color;
|
||||||
|
|
||||||
|
// hsl is better than hex between conversions
|
||||||
|
if (data && data.hsl) {
|
||||||
|
color = tinycolor(data.hsl);
|
||||||
|
} else if (data && data.hex && data.hex.length > 0) {
|
||||||
|
color = tinycolor(data.hex);
|
||||||
|
} else {
|
||||||
|
color = tinycolor(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color && (color._a === undefined || color._a === null)) {
|
||||||
|
color.setAlpha(alpha || 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hsl = color.toHsl();
|
||||||
|
const hsv = color.toHsv();
|
||||||
|
|
||||||
|
if (hsl.s === 0) {
|
||||||
|
hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when the hsv.v is less than 0.0164 (base on test)
|
||||||
|
// because of possible loss of precision
|
||||||
|
// the result of hue and saturation would be miscalculated
|
||||||
|
if (hsv.v < 0.0164) {
|
||||||
|
hsv.h = data.h || (data.hsv && data.hsv.h) || 0;
|
||||||
|
hsv.s = data.s || (data.hsv && data.hsv.s) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hsl.l < 0.01) {
|
||||||
|
hsl.h = data.h || (data.hsl && data.hsl.h) || 0;
|
||||||
|
hsl.s = data.s || (data.hsl && data.hsl.s) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hsl: hsl,
|
||||||
|
hex: color.toHexString().toUpperCase(),
|
||||||
|
rgba: color.toRgb(),
|
||||||
|
hsv: hsv,
|
||||||
|
oldHue: data.h || oldHue || hsl.h,
|
||||||
|
source: data.source,
|
||||||
|
a: data.a || color.getAlpha()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ColorPicker',
|
name: 'ColorPicker',
|
||||||
components: { Dropdown, DropdownMenu, Slider, Confirm, RecommendColors },
|
components: { Dropdown, DropdownMenu, Confirm, RecommendColors, Saturation, Hue, Alpha },
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: String
|
type: Object
|
||||||
},
|
},
|
||||||
alpha: {
|
alpha: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -81,10 +137,8 @@
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
val: _colorChange(this.value),
|
||||||
prefixCls: prefixCls,
|
prefixCls: prefixCls,
|
||||||
currentValue: this.value,
|
|
||||||
hueNumber: 0,
|
|
||||||
alphaNumber: 0,
|
|
||||||
recommendedColor: [
|
recommendedColor: [
|
||||||
'#2d8cf0',
|
'#2d8cf0',
|
||||||
'#19be6b',
|
'#19be6b',
|
||||||
|
@ -110,6 +164,15 @@
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
saturationColors: {
|
||||||
|
get () {
|
||||||
|
return this.val;
|
||||||
|
},
|
||||||
|
set (newVal) {
|
||||||
|
this.val = newVal;
|
||||||
|
this.$emit('input', newVal);
|
||||||
|
}
|
||||||
|
},
|
||||||
wrapClasses () {
|
wrapClasses () {
|
||||||
return [
|
return [
|
||||||
`${prefixCls}-rel`,
|
`${prefixCls}-rel`,
|
||||||
|
@ -129,8 +192,41 @@
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
value (newVal) {
|
||||||
|
this.val = _colorChange(newVal);
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
childChange (data) {
|
||||||
|
this.colorChange(data);
|
||||||
|
},
|
||||||
|
colorChange (data, oldHue) {
|
||||||
|
this.oldHue = this.saturationColors.hsl.h;
|
||||||
|
this.saturationColors = _colorChange(data, oldHue || this.oldHue);
|
||||||
|
},
|
||||||
|
isValidHex (hex) {
|
||||||
|
return tinycolor(hex).isValid();
|
||||||
|
},
|
||||||
|
simpleCheckForValidColor (data) {
|
||||||
|
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
|
||||||
|
let checked = 0;
|
||||||
|
let passed = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < keysToCheck.length; i++) {
|
||||||
|
const letter = keysToCheck[i];
|
||||||
|
if (data[letter]) {
|
||||||
|
checked++;
|
||||||
|
if (!isNaN(data[letter])) {
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checked === passed) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
86
src/components/color-picker/hue.vue
Normal file
86
src/components/color-picker/hue.vue
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<template>
|
||||||
|
<div class="ivu-color-picker-hue">
|
||||||
|
<div class="ivu-color-picker-hue-container" ref="container"
|
||||||
|
@mousedown="handleMouseDown"
|
||||||
|
@touchmove="handleChange"
|
||||||
|
@touchstart="handleChange">
|
||||||
|
<div class="ivu-color-picker-hue-pointer" :style="{top: 0, left: pointerLeft}">
|
||||||
|
<div class="ivu-color-picker-hue-picker"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Hue',
|
||||||
|
props: {
|
||||||
|
value: Object
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
oldHue: 0,
|
||||||
|
pullDirection: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
colors () {
|
||||||
|
const h = this.value.hsl.h;
|
||||||
|
if (h !== 0 && h - this.oldHue > 0) this.pullDirection = 'right';
|
||||||
|
if (h !== 0 && h - this.oldHue < 0) this.pullDirection = 'left';
|
||||||
|
this.oldHue = h;
|
||||||
|
|
||||||
|
return this.value;
|
||||||
|
},
|
||||||
|
pointerLeft () {
|
||||||
|
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') return '100%';
|
||||||
|
return (this.colors.hsl.h * 100) / 360 + '%';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange (e, skip) {
|
||||||
|
!skip && e.preventDefault();
|
||||||
|
|
||||||
|
const container = this.$refs.container;
|
||||||
|
const containerWidth = container.clientWidth;
|
||||||
|
|
||||||
|
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||||
|
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||||
|
const left = pageX - xOffset;
|
||||||
|
|
||||||
|
let h;
|
||||||
|
let percent;
|
||||||
|
|
||||||
|
if (left < 0) {
|
||||||
|
h = 0;
|
||||||
|
} else if (left > containerWidth) {
|
||||||
|
h = 360;
|
||||||
|
} else {
|
||||||
|
percent = left * 100 / containerWidth;
|
||||||
|
h = (360 * percent / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.colors.hsl.h !== h) {
|
||||||
|
this.$emit('change', {
|
||||||
|
h: h,
|
||||||
|
s: this.colors.hsl.s,
|
||||||
|
l: this.colors.hsl.l,
|
||||||
|
a: this.colors.hsl.a,
|
||||||
|
source: 'hsl'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMouseDown (e) {
|
||||||
|
this.handleChange(e, true);
|
||||||
|
window.addEventListener('mousemove', this.handleChange);
|
||||||
|
window.addEventListener('mouseup', this.handleMouseUp);
|
||||||
|
},
|
||||||
|
handleMouseUp () {
|
||||||
|
this.unbindEventListeners();
|
||||||
|
},
|
||||||
|
unbindEventListeners () {
|
||||||
|
window.removeEventListener('mousemove', this.handleChange);
|
||||||
|
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
98
src/components/color-picker/saturation.vue
Normal file
98
src/components/color-picker/saturation.vue
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<div class="ivu-color-picker-saturation-wrapper">
|
||||||
|
<div
|
||||||
|
class="ivu-color-picker-saturation"
|
||||||
|
:style="{background: bgColor}"
|
||||||
|
ref="container"
|
||||||
|
@mousedown="handleMouseDown">
|
||||||
|
<div class="ivu-color-picker-saturation--white"></div>
|
||||||
|
<div class="ivu-color-picker-saturation--black"></div>
|
||||||
|
<div class="ivu-color-picker-saturation-pointer" :style="{top: pointerTop, left: pointerLeft}">
|
||||||
|
<div class="ivu-color-picker-saturation-circle"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import throttle from '../../utils/throttle';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Saturation',
|
||||||
|
props: {
|
||||||
|
value: Object
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
colors () {
|
||||||
|
return this.value;
|
||||||
|
},
|
||||||
|
bgColor () {
|
||||||
|
return `hsl(${this.colors.hsv.h}, 100%, 50%)`;
|
||||||
|
},
|
||||||
|
pointerTop () {
|
||||||
|
return (-(this.colors.hsv.v * 100) + 1) + 100 + '%';
|
||||||
|
},
|
||||||
|
pointerLeft () {
|
||||||
|
return this.colors.hsv.s * 100 + '%';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
throttle: throttle((fn, data) => {fn(data);}, 20,
|
||||||
|
{
|
||||||
|
'leading': true,
|
||||||
|
'trailing': false
|
||||||
|
}),
|
||||||
|
handleChange (e, skip) {
|
||||||
|
!skip && e.preventDefault();
|
||||||
|
const container = this.$refs.container;
|
||||||
|
const containerWidth = container.clientWidth;
|
||||||
|
const containerHeight = container.clientHeight;
|
||||||
|
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||||
|
const yOffset = container.getBoundingClientRect().top + window.pageYOffset;
|
||||||
|
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||||
|
const pageY = e.pageY || (e.touches ? e.touches[0].pageY : 0);
|
||||||
|
let left = pageX - xOffset;
|
||||||
|
let top = pageY - yOffset;
|
||||||
|
if (left < 0) {
|
||||||
|
left = 0;
|
||||||
|
} else if (left > containerWidth) {
|
||||||
|
left = containerWidth;
|
||||||
|
} else if (top < 0) {
|
||||||
|
top = 0;
|
||||||
|
} else if (top > containerHeight) {
|
||||||
|
top = containerHeight;
|
||||||
|
}
|
||||||
|
const saturation = left / containerWidth;
|
||||||
|
let bright = -(top / containerHeight) + 1;
|
||||||
|
bright = bright > 0 ? bright : 0;
|
||||||
|
bright = bright > 1 ? 1 : bright;
|
||||||
|
this.throttle(this.onChange, {
|
||||||
|
h: this.colors.hsv.h,
|
||||||
|
s: saturation,
|
||||||
|
v: bright,
|
||||||
|
a: this.colors.hsv.a,
|
||||||
|
source: 'hsva'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onChange (param) {
|
||||||
|
this.$emit('change', param);
|
||||||
|
},
|
||||||
|
handleMouseDown () {
|
||||||
|
// this.handleChange(e, true)
|
||||||
|
window.addEventListener('mousemove', this.handleChange);
|
||||||
|
window.addEventListener('mouseup', this.handleChange);
|
||||||
|
window.addEventListener('mouseup', this.handleMouseUp);
|
||||||
|
},
|
||||||
|
handleMouseUp () {
|
||||||
|
this.unbindEventListeners();
|
||||||
|
},
|
||||||
|
unbindEventListeners () {
|
||||||
|
window.removeEventListener('mousemove', this.handleChange);
|
||||||
|
window.removeEventListener('mouseup', this.handleChange);
|
||||||
|
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -24,30 +24,31 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-picker{
|
&-picker{
|
||||||
padding: 8px 8px 0;
|
padding: 4px 8px 0;
|
||||||
&-panel{
|
&-panel{
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: #47cb89;
|
box-sizing: initial;
|
||||||
border-radius: 50%;
|
position: relative;
|
||||||
}
|
}
|
||||||
&-hue-slider{
|
&-hue-slider, &-alpha-slider{
|
||||||
|
height: 10px;
|
||||||
}
|
margin-top: 8px;
|
||||||
&-alpha-slider{
|
position: relative;
|
||||||
|
|
||||||
}
|
}
|
||||||
&-colors{
|
&-colors{
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
overflow: hidden;
|
||||||
span{
|
span{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 18px;
|
width: 20px;
|
||||||
height: 18px;
|
height: 20px;
|
||||||
|
float: left;
|
||||||
em{
|
em{
|
||||||
display: block;
|
display: block;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
margin: 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: inset 0 0 0 1px rgba(0,0,0,.15);
|
box-shadow: inset 0 0 0 1px rgba(0,0,0,.15);
|
||||||
|
@ -58,4 +59,121 @@
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-saturation{
|
||||||
|
&-wrapper{
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 75%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
&, &--white, &--black{
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
&--white{
|
||||||
|
background: linear-gradient(to right, #fff, rgba(255,255,255,0));
|
||||||
|
}
|
||||||
|
&--black{
|
||||||
|
background: linear-gradient(to top, #000, rgba(0,0,0,0));
|
||||||
|
}
|
||||||
|
&-pointer{
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
&-circle{
|
||||||
|
cursor: head;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0,0,0,.3), 0 0 1px 2px rgba(0,0,0,.4);
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translate(-2px, -2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-hue{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
||||||
|
&-container{
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 2px;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
&-pointer{
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
&-picker{
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 1px;
|
||||||
|
width: 4px;
|
||||||
|
border-radius: 1px;
|
||||||
|
height: 8px;
|
||||||
|
box-shadow: 0 0 2px rgba(0, 0, 0, .6);
|
||||||
|
background: #fff;
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-alpha{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
&-checkboard-wrap{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
&-checkerboard{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
|
||||||
|
}
|
||||||
|
&-gradient{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
&-container{
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
&-pointer{
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
&-picker{
|
||||||
|
cursor: pointer;
|
||||||
|
width: 4px;
|
||||||
|
border-radius: 1px;
|
||||||
|
height: 8px;
|
||||||
|
box-shadow: 0 0 2px rgba(0, 0, 0, .6);
|
||||||
|
background: #fff;
|
||||||
|
margin-top: 1px;
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
439
src/utils/throttle.js
Normal file
439
src/utils/throttle.js
Normal file
|
@ -0,0 +1,439 @@
|
||||||
|
/**
|
||||||
|
* lodash (Custom Build) <https://lodash.com/>
|
||||||
|
* Build: `lodash modularize exports="npm" -o ./`
|
||||||
|
* Copyright jQuery Foundation and other contributors <https://jquery.org/>
|
||||||
|
* Released under MIT license <https://lodash.com/license>
|
||||||
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||||
|
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Used as the `TypeError` message for "Functions" methods. */
|
||||||
|
var FUNC_ERROR_TEXT = 'Expected a function';
|
||||||
|
|
||||||
|
/** Used as references for various `Number` constants. */
|
||||||
|
var NAN = 0 / 0;
|
||||||
|
|
||||||
|
/** `Object#toString` result references. */
|
||||||
|
var symbolTag = '[object Symbol]';
|
||||||
|
|
||||||
|
/** Used to match leading and trailing whitespace. */
|
||||||
|
var reTrim = /^\s+|\s+$/g;
|
||||||
|
|
||||||
|
/** Used to detect bad signed hexadecimal string values. */
|
||||||
|
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
|
||||||
|
|
||||||
|
/** Used to detect binary string values. */
|
||||||
|
var reIsBinary = /^0b[01]+$/i;
|
||||||
|
|
||||||
|
/** Used to detect octal string values. */
|
||||||
|
var reIsOctal = /^0o[0-7]+$/i;
|
||||||
|
|
||||||
|
/** Built-in method references without a dependency on `root`. */
|
||||||
|
var freeParseInt = parseInt;
|
||||||
|
|
||||||
|
/** Detect free variable `global` from Node.js. */
|
||||||
|
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
|
||||||
|
|
||||||
|
/** Detect free variable `self`. */
|
||||||
|
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
|
||||||
|
|
||||||
|
/** Used as a reference to the global object. */
|
||||||
|
var root = freeGlobal || freeSelf || Function('return this')();
|
||||||
|
|
||||||
|
/** Used for built-in method references. */
|
||||||
|
var objectProto = Object.prototype;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to resolve the
|
||||||
|
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
||||||
|
* of values.
|
||||||
|
*/
|
||||||
|
var objectToString = objectProto.toString;
|
||||||
|
|
||||||
|
/* Built-in method references for those with the same name as other `lodash` methods. */
|
||||||
|
var nativeMax = Math.max,
|
||||||
|
nativeMin = Math.min;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the timestamp of the number of milliseconds that have elapsed since
|
||||||
|
* the Unix epoch (1 January 1970 00:00:00 UTC).
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf _
|
||||||
|
* @since 2.4.0
|
||||||
|
* @category Date
|
||||||
|
* @returns {number} Returns the timestamp.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* _.defer(function(stamp) {
|
||||||
|
* console.log(_.now() - stamp);
|
||||||
|
* }, _.now());
|
||||||
|
* // => Logs the number of milliseconds it took for the deferred invocation.
|
||||||
|
*/
|
||||||
|
var now = function() {
|
||||||
|
return root.Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a debounced function that delays invoking `func` until after `wait`
|
||||||
|
* milliseconds have elapsed since the last time the debounced function was
|
||||||
|
* invoked. The debounced function comes with a `cancel` method to cancel
|
||||||
|
* delayed `func` invocations and a `flush` method to immediately invoke them.
|
||||||
|
* Provide `options` to indicate whether `func` should be invoked on the
|
||||||
|
* leading and/or trailing edge of the `wait` timeout. The `func` is invoked
|
||||||
|
* with the last arguments provided to the debounced function. Subsequent
|
||||||
|
* calls to the debounced function return the result of the last `func`
|
||||||
|
* invocation.
|
||||||
|
*
|
||||||
|
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
||||||
|
* invoked on the trailing edge of the timeout only if the debounced function
|
||||||
|
* is invoked more than once during the `wait` timeout.
|
||||||
|
*
|
||||||
|
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
||||||
|
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
|
||||||
|
*
|
||||||
|
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
||||||
|
* for details over the differences between `_.debounce` and `_.throttle`.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf _
|
||||||
|
* @since 0.1.0
|
||||||
|
* @category Function
|
||||||
|
* @param {Function} func The function to debounce.
|
||||||
|
* @param {number} [wait=0] The number of milliseconds to delay.
|
||||||
|
* @param {Object} [options={}] The options object.
|
||||||
|
* @param {boolean} [options.leading=false]
|
||||||
|
* Specify invoking on the leading edge of the timeout.
|
||||||
|
* @param {number} [options.maxWait]
|
||||||
|
* The maximum time `func` is allowed to be delayed before it's invoked.
|
||||||
|
* @param {boolean} [options.trailing=true]
|
||||||
|
* Specify invoking on the trailing edge of the timeout.
|
||||||
|
* @returns {Function} Returns the new debounced function.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // Avoid costly calculations while the window size is in flux.
|
||||||
|
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
|
||||||
|
*
|
||||||
|
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
|
||||||
|
* jQuery(element).on('click', _.debounce(sendMail, 300, {
|
||||||
|
* 'leading': true,
|
||||||
|
* 'trailing': false
|
||||||
|
* }));
|
||||||
|
*
|
||||||
|
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
|
||||||
|
* var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
|
||||||
|
* var source = new EventSource('/stream');
|
||||||
|
* jQuery(source).on('message', debounced);
|
||||||
|
*
|
||||||
|
* // Cancel the trailing debounced invocation.
|
||||||
|
* jQuery(window).on('popstate', debounced.cancel);
|
||||||
|
*/
|
||||||
|
function debounce(func, wait, options) {
|
||||||
|
var lastArgs,
|
||||||
|
lastThis,
|
||||||
|
maxWait,
|
||||||
|
result,
|
||||||
|
timerId,
|
||||||
|
lastCallTime,
|
||||||
|
lastInvokeTime = 0,
|
||||||
|
leading = false,
|
||||||
|
maxing = false,
|
||||||
|
trailing = true;
|
||||||
|
|
||||||
|
if (typeof func != 'function') {
|
||||||
|
throw new TypeError(FUNC_ERROR_TEXT);
|
||||||
|
}
|
||||||
|
wait = toNumber(wait) || 0;
|
||||||
|
if (isObject(options)) {
|
||||||
|
leading = !!options.leading;
|
||||||
|
maxing = 'maxWait' in options;
|
||||||
|
maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
|
||||||
|
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeFunc(time) {
|
||||||
|
var args = lastArgs,
|
||||||
|
thisArg = lastThis;
|
||||||
|
|
||||||
|
lastArgs = lastThis = undefined;
|
||||||
|
lastInvokeTime = time;
|
||||||
|
result = func.apply(thisArg, args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function leadingEdge(time) {
|
||||||
|
// Reset any `maxWait` timer.
|
||||||
|
lastInvokeTime = time;
|
||||||
|
// Start the timer for the trailing edge.
|
||||||
|
timerId = setTimeout(timerExpired, wait);
|
||||||
|
// Invoke the leading edge.
|
||||||
|
return leading ? invokeFunc(time) : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function remainingWait(time) {
|
||||||
|
var timeSinceLastCall = time - lastCallTime,
|
||||||
|
timeSinceLastInvoke = time - lastInvokeTime,
|
||||||
|
result = wait - timeSinceLastCall;
|
||||||
|
|
||||||
|
return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldInvoke(time) {
|
||||||
|
var timeSinceLastCall = time - lastCallTime,
|
||||||
|
timeSinceLastInvoke = time - lastInvokeTime;
|
||||||
|
|
||||||
|
// Either this is the first call, activity has stopped and we're at the
|
||||||
|
// trailing edge, the system time has gone backwards and we're treating
|
||||||
|
// it as the trailing edge, or we've hit the `maxWait` limit.
|
||||||
|
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
|
||||||
|
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
|
||||||
|
}
|
||||||
|
|
||||||
|
function timerExpired() {
|
||||||
|
var time = now();
|
||||||
|
if (shouldInvoke(time)) {
|
||||||
|
return trailingEdge(time);
|
||||||
|
}
|
||||||
|
// Restart the timer.
|
||||||
|
timerId = setTimeout(timerExpired, remainingWait(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
function trailingEdge(time) {
|
||||||
|
timerId = undefined;
|
||||||
|
|
||||||
|
// Only invoke if we have `lastArgs` which means `func` has been
|
||||||
|
// debounced at least once.
|
||||||
|
if (trailing && lastArgs) {
|
||||||
|
return invokeFunc(time);
|
||||||
|
}
|
||||||
|
lastArgs = lastThis = undefined;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
if (timerId !== undefined) {
|
||||||
|
clearTimeout(timerId);
|
||||||
|
}
|
||||||
|
lastInvokeTime = 0;
|
||||||
|
lastArgs = lastCallTime = lastThis = timerId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flush() {
|
||||||
|
return timerId === undefined ? result : trailingEdge(now());
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounced() {
|
||||||
|
var time = now(),
|
||||||
|
isInvoking = shouldInvoke(time);
|
||||||
|
|
||||||
|
lastArgs = arguments;
|
||||||
|
lastThis = this;
|
||||||
|
lastCallTime = time;
|
||||||
|
|
||||||
|
if (isInvoking) {
|
||||||
|
if (timerId === undefined) {
|
||||||
|
return leadingEdge(lastCallTime);
|
||||||
|
}
|
||||||
|
if (maxing) {
|
||||||
|
// Handle invocations in a tight loop.
|
||||||
|
timerId = setTimeout(timerExpired, wait);
|
||||||
|
return invokeFunc(lastCallTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timerId === undefined) {
|
||||||
|
timerId = setTimeout(timerExpired, wait);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
debounced.cancel = cancel;
|
||||||
|
debounced.flush = flush;
|
||||||
|
return debounced;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a throttled function that only invokes `func` at most once per
|
||||||
|
* every `wait` milliseconds. The throttled function comes with a `cancel`
|
||||||
|
* method to cancel delayed `func` invocations and a `flush` method to
|
||||||
|
* immediately invoke them. Provide `options` to indicate whether `func`
|
||||||
|
* should be invoked on the leading and/or trailing edge of the `wait`
|
||||||
|
* timeout. The `func` is invoked with the last arguments provided to the
|
||||||
|
* throttled function. Subsequent calls to the throttled function return the
|
||||||
|
* result of the last `func` invocation.
|
||||||
|
*
|
||||||
|
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
||||||
|
* invoked on the trailing edge of the timeout only if the throttled function
|
||||||
|
* is invoked more than once during the `wait` timeout.
|
||||||
|
*
|
||||||
|
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
||||||
|
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
|
||||||
|
*
|
||||||
|
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
||||||
|
* for details over the differences between `_.throttle` and `_.debounce`.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf _
|
||||||
|
* @since 0.1.0
|
||||||
|
* @category Function
|
||||||
|
* @param {Function} func The function to throttle.
|
||||||
|
* @param {number} [wait=0] The number of milliseconds to throttle invocations to.
|
||||||
|
* @param {Object} [options={}] The options object.
|
||||||
|
* @param {boolean} [options.leading=true]
|
||||||
|
* Specify invoking on the leading edge of the timeout.
|
||||||
|
* @param {boolean} [options.trailing=true]
|
||||||
|
* Specify invoking on the trailing edge of the timeout.
|
||||||
|
* @returns {Function} Returns the new throttled function.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // Avoid excessively updating the position while scrolling.
|
||||||
|
* jQuery(window).on('scroll', _.throttle(updatePosition, 100));
|
||||||
|
*
|
||||||
|
* // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
|
||||||
|
* var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
|
||||||
|
* jQuery(element).on('click', throttled);
|
||||||
|
*
|
||||||
|
* // Cancel the trailing throttled invocation.
|
||||||
|
* jQuery(window).on('popstate', throttled.cancel);
|
||||||
|
*/
|
||||||
|
function throttle(func, wait, options) {
|
||||||
|
var leading = true,
|
||||||
|
trailing = true;
|
||||||
|
|
||||||
|
if (typeof func != 'function') {
|
||||||
|
throw new TypeError(FUNC_ERROR_TEXT);
|
||||||
|
}
|
||||||
|
if (isObject(options)) {
|
||||||
|
leading = 'leading' in options ? !!options.leading : leading;
|
||||||
|
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
||||||
|
}
|
||||||
|
return debounce(func, wait, {
|
||||||
|
'leading': leading,
|
||||||
|
'maxWait': wait,
|
||||||
|
'trailing': trailing
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if `value` is the
|
||||||
|
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
|
||||||
|
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf _
|
||||||
|
* @since 0.1.0
|
||||||
|
* @category Lang
|
||||||
|
* @param {*} value The value to check.
|
||||||
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* _.isObject({});
|
||||||
|
* // => true
|
||||||
|
*
|
||||||
|
* _.isObject([1, 2, 3]);
|
||||||
|
* // => true
|
||||||
|
*
|
||||||
|
* _.isObject(_.noop);
|
||||||
|
* // => true
|
||||||
|
*
|
||||||
|
* _.isObject(null);
|
||||||
|
* // => false
|
||||||
|
*/
|
||||||
|
function isObject(value) {
|
||||||
|
var type = typeof value;
|
||||||
|
return !!value && (type == 'object' || type == 'function');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if `value` is object-like. A value is object-like if it's not `null`
|
||||||
|
* and has a `typeof` result of "object".
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf _
|
||||||
|
* @since 4.0.0
|
||||||
|
* @category Lang
|
||||||
|
* @param {*} value The value to check.
|
||||||
|
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* _.isObjectLike({});
|
||||||
|
* // => true
|
||||||
|
*
|
||||||
|
* _.isObjectLike([1, 2, 3]);
|
||||||
|
* // => true
|
||||||
|
*
|
||||||
|
* _.isObjectLike(_.noop);
|
||||||
|
* // => false
|
||||||
|
*
|
||||||
|
* _.isObjectLike(null);
|
||||||
|
* // => false
|
||||||
|
*/
|
||||||
|
function isObjectLike(value) {
|
||||||
|
return !!value && typeof value == 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if `value` is classified as a `Symbol` primitive or object.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf _
|
||||||
|
* @since 4.0.0
|
||||||
|
* @category Lang
|
||||||
|
* @param {*} value The value to check.
|
||||||
|
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* _.isSymbol(Symbol.iterator);
|
||||||
|
* // => true
|
||||||
|
*
|
||||||
|
* _.isSymbol('abc');
|
||||||
|
* // => false
|
||||||
|
*/
|
||||||
|
function isSymbol(value) {
|
||||||
|
return typeof value == 'symbol' ||
|
||||||
|
(isObjectLike(value) && objectToString.call(value) == symbolTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts `value` to a number.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @memberOf _
|
||||||
|
* @since 4.0.0
|
||||||
|
* @category Lang
|
||||||
|
* @param {*} value The value to process.
|
||||||
|
* @returns {number} Returns the number.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* _.toNumber(3.2);
|
||||||
|
* // => 3.2
|
||||||
|
*
|
||||||
|
* _.toNumber(Number.MIN_VALUE);
|
||||||
|
* // => 5e-324
|
||||||
|
*
|
||||||
|
* _.toNumber(Infinity);
|
||||||
|
* // => Infinity
|
||||||
|
*
|
||||||
|
* _.toNumber('3.2');
|
||||||
|
* // => 3.2
|
||||||
|
*/
|
||||||
|
function toNumber(value) {
|
||||||
|
if (typeof value == 'number') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (isSymbol(value)) {
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
if (isObject(value)) {
|
||||||
|
var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
|
||||||
|
value = isObject(other) ? (other + '') : other;
|
||||||
|
}
|
||||||
|
if (typeof value != 'string') {
|
||||||
|
return value === 0 ? value : +value;
|
||||||
|
}
|
||||||
|
value = value.replace(reTrim, '');
|
||||||
|
var isBinary = reIsBinary.test(value);
|
||||||
|
return (isBinary || reIsOctal.test(value))
|
||||||
|
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
|
||||||
|
: (reIsBadHex.test(value) ? NAN : +value);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = throttle;
|
Loading…
Reference in a new issue