从零开始实现一个颜色选择器(原生JavaScript实现),2024年最新web前端需要学哪些知识

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

border-radius: 5px;

z-index: 10;

padding: 7px;

text-align: left;

}

现在我们再来确定容器元素中都有哪些元素,首先是一个颜色面板,颜色面板又包含一个容器元素,我们可以看到,颜色面板很像是三种背景色叠加出来的效果,不用怀疑,大胆的说,是的没错,就是三种背景色叠加出来的,所以我们就需要一个容器元素,然后容器元素里面又包含 2 个面板元素,容器元素的背景色加上 2 个面板元素叠加出来就是这种效果。一个白色的背景加一个黑色的就能叠加看到我们想要的效果。

比如我们先来看看一个示例:

.panel {

width: 280px;

height: 180px;

position: relative;

border: 1px solid #fff;

background-color: rgb(255, 166, 0);

}

.panel > div.white-panel,

.panel > div.black-panel {

position: absolute;

left: 0;

right: 0;

top: 0;

bottom: 0;

}

.white-panel {

background: linear-gradient(90deg, #fff, rgba(255, 255, 255, 0));

}

.black-panel {

background: linear-gradient(0deg, #000, transparent);

}

这里可能又涉及到一个知识点,那就是渐变颜色,这里就不做细讲,感兴趣的可查看文档。

所以我们的结构应该是如下:

根据前面那个示例,我们很快就能写出这个颜色面板了,不过我们还少了一个,也就是在颜色面板区域之内的拖动元素,或者我们可以称之为游标元素。

.ew-color-picker-panel {

width: 280px;

height: 180px;

position: relative;

border: 1px solid #fff;

background-color: rgb(255, 166, 0);

cursor: pointer;

}

.ew-color-picker-panel > div.ew-color-picker-white-panel,

.ew-color-picker-panel > div.ew-color-picker-black-panel {

position: absolute;

left: 0;

right: 0;

top: 0;

bottom: 0;

}

.ew-color-picker-white-panel {

background: linear-gradient(90deg, #fff, rgba(255, 255, 255, 0));

}

.ew-color-picker-black-panel {

background: linear-gradient(0deg, #000, transparent);

}

好了,现在我可以回答之前那个留下的问题了,为什么要使用标准盒子模型而不是 IE 标准盒子模型。这是因为这里我们会通过 js 动态去计算游标元素拖动的距离,如果是 IE 标准盒子模型,则会考虑边框的大小以及间距的大小,这无疑给我们计算拖动距离增加了难度,所以为了简便化,我们使用的是标准盒子模型。

现在我们再来加上这个游标元素吧,因为它是在颜色面板内动态改变的,通常我们要让一个元素在父元素当中进行移动,那么我们很明显就想到了子元素使用绝对定位,父元素加一个除了静态定位static以外的定位,通常我们用相对定位,这里也不例外。这也就是我们给.ew-color-picker-panel添加一个相对定位position: relative;的原因。

这里需要注意了,游标元素设置的宽高会影响我们后续计算,所以在这里设置的宽高是多少,后续计算就要将它的宽高考虑在内,这个到后面会细讲,现在,我们还是编写该元素的样式吧。

.ew-color-picker-panel-cursor {

width: 4px;

height: 4px;

border-radius: 50%;

position: absolute;

left: 100%;

top: 0;

transform: translate(-4px, -4px);

box-shadow: 0 0 0 3px #fff, inset 0 0 2px 2px rgb(0 0 0 / 40%),

/等价于rgba(0,0,0,0.4)/ 0 0 2px 3px rgb(0 0 0 / 50%); /等价于rgba(0,0,0,0.5)/

cursor: default;

}

游标元素,我们看起来就像是一个小圆圈,所以我们给的宽高不是很多,只有 4px,既然是圆,我们都知道可以使用border-radius50%即可以将一个元素变成圆。接下来就是阴影部分,这样就实现了我们的小圆圈。当然我们不一定非要实现这样的效果,但是为了还原颜色选择器本身,也方便后续的计算,所以我们还是采用原本的样式。

色阶柱


接下来,我们来看一下色阶柱也就是色调柱的实现。看到这个图,我们应该可以很清晰的分出色阶柱包含了 2 个部分,第一个部分就是柱形部分,称之为 bar,第二个部分就是拖动滑块部分,称之为 thumb。然后我们外加一个容器元素用于包含色阶柱和透明柱,所以我们可以确定色阶柱的结构如下:

然后我们来确定样式的实现,首先整个色阶柱是垂直布局的,所以我们应该知道它就是有一个固定宽度,然后高度等价于颜色面板的矩形,它的背景色通过一种渐变色来实现,实际上就是红橙黄绿青蓝紫七种颜色的混合,也就类似彩虹。这每一种颜色都有不同的比例。其次我们还要知道滑块部分是需要动态拖动的。在这里我们可以想象得到色阶柱可以是水平或者垂直布局的,目前我们先实现垂直布局(为了区分给容器元素加一个类名 ew-is-vertical)。所以滑块的动态改变部分应该是 top 值。现在我们来看样式:

.ew-color-slider,

.ew-color-slider-bar {

position: relative;

}

.ew-color-slider.ew-is-vertical {

width: 28px;

height: 100%;

cursor: pointer;

float: right;

}

.ew-color-slider.ew-is-vertical .ew-color-slider-bar {

width: 12px;

height: 100%;

float: left;

margin-left: 3px;

background: linear-gradient(

180deg,

#f00 0,

#ff0 17%,

#0f0 33%,

#0ff 50%,

#00f 67%,

#f0f 83%,

#f00

);

}

.ew-color-slider-thumb {

background-color: #fff;

border-radius: 4px;

position: absolute;

box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);

border: 1px solid #dcdee2;

left: 0;

top: 0;

box-sizing: border-box;

position: absolute;

}

到目前为止,我们色阶柱就算是实现了,接下来来看透明度柱的实现。

透明度柱


透明度柱的实现原理跟色阶柱很相似,首先我们可以看到透明度柱会有一个透明的背景,这个背景很显然是一个图片,其次它还会有一个背景色条,取决于当且色阶柱处于哪种色调,然后同样还是与色阶柱一样有一个滑块,同样也是有垂直布局和水平布局,改变 top 值。所以我们得到结构如下所示:

在这里,我们需要注意的一点就是背景色条的背景色是动态改变,这将在后面会讲到。背景色条,我们同样是通过线性渐变来实现的。让我们来看看样式吧:

.ew-alpha-slider-bar {

width: 12px;

height: 100%;

float: left;

position: relative;

}

.ew-alpha-slider-wrapper,

.ew-alpha-slider-bg {

position: absolute;

left: 0;

top: 0;

bottom: 0;

right: 0;

}

.ew-alpha-slider-bar.ew-is-vertical .ew-alpha-slider-bg {

/* 这里先暂时写死 */

background: linear-gradient(

to top,

rgba(255, 0, 0, 0) 0%,

rgba(255, 0, 0) 100%

);

}

.ew-alpha-slider-wrapper {

background: url(“data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==”);

}

.ew-alpha-slider-thumb {

background-color: #fff;

border-radius: 4px;

position: absolute;

box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);

border: 1px solid #dcdee2;

left: 0;

top: 0;

box-sizing: border-box;

position: absolute;

}

好了,到目前为止,我们的透明度柱也就实现了,接下来我们来看输入框的实现。

输入框与按钮


输入框比较简单,我想没什么好说的,这个输入框也可以自定义,它的结构无非就是如下:

它和清空与确定按钮元素排在一行,因此我们用一个容器元素来包裹它们,结构应该如下:

清空

确定

然后样式也没有什么好分析的,都是一些基础样式,我们继续编写代码。如下:

.ew-color-drop-container {

margin-top: 6px;

padding-top: 4px;

min-height: 28px;

border-top: 1px solid #cdcdcd;

position: relative;

}

.ew-color-input {

display: inline-block;

padding: 8px 12px;

border: 1px solid #e9ebee;

border-radius: 4px;

outline: none;

width: 160px;

height: 28px;

line-height: 28px;

border: 1px solid #dcdfe6;

padding: 0 5px;

-webkit-transition: border-color 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);

transition: border-color 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);

border-radius: 5px;

background-color: #fff;

}

.ew-color-drop-btn-group {

position: absolute;

right: 0;

top: 5px;

}

.ew-color-drop-btn {

padding: 5px;

font-size: 12px;

border-radius: 3px;

-webkit-transition: 0.1s;

transition: 0.1s;

font-weight: 500;

margin: 0;

white-space: nowrap;

color: #606266;

border: 1px solid #dcdfe6;

letter-spacing: 1px;

text-align: center;

cursor: pointer;

}

.ew-color-clear {

color: #4096ef;

border-color: transparent;

background-color: transparent;

padding-left: 0;

padding-right: 0;

}

.ew-color-clear:hover {

color: #66b1ff;

}

.ew-color-sure {

margin-left: 10px;

}

.ew-color-sure {

border-color: #4096ef;

color: #4096ef;

}

输入框和按钮我们就已经完成了,接下来我们再来看预定义颜色元素呢。

预定义颜色


预定义颜色元素实现起来也比较简单,就是一个容器元素,然后包含多个子元素,可能稍微难一点的就是子元素的样式我们分为四种情况,第一种就是默认的样式,第二种就是禁止点击的样式,除此之外,我们还加了一个颜色透明度之间的区别,然后最后就是选中样式。不多说,我们可以先写 4 个子元素来分别代表四种情况的样式。如下:

class=“ew-pre-define-color ew-pre-define-color-disabled”

tabindex=“2”

class=“ew-pre-define-color ew-pre-define-color-active”

tabindex=“3”

接下来,我们来看样式的实现:

.ew-pre-define-color-container {

width: 280px;

font-size: 12px;

margin-top: 8px;

}

.ew-pre-define-color-container::after {

content: “”;

display: table;

height: 0;

visibility: hidden;

clear: both;

}

.ew-pre-define-color-container .ew-pre-define-color {

margin: 0 0 8px 8px;

width: 20px;

height: 20px;

border-radius: 4px;

border: 1px solid #9b979b;

cursor: pointer;

float: left;

}

.ew-pre-define-color-container .ew-pre-define-color:hover {

opacity: 0.8;

}

.ew-pre-define-color-active {

box-shadow: 0 0 3px 2px #409eff;

}

.ew-pre-define-color:nth-child(10n + 1) {

margin-left: 0;

}

.ew-pre-define-color.ew-has-alpha {

background: url(“data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==”);

}

.ew-pre-define-color.ew-pre-define-color-disabled {

cursor: not-allowed;

}

样式和布局就到此结束了,接下来才是我们的重点,也就是实现颜色选择器的功能。

JavaScript

==========

工具方法


首先用一个空对象来管理工具方法。如下:

const util = Object.create(null);

然后有如下方法:

const util = Object.create(null);

const _toString = Object.prototype.toString;

let addMethod = (instance, method, func) => {

instance.prototype[method] = func;

return instance;

};

[“Number”, “String”, “Function”, “Undefined”, “Boolean”].forEach(

(type) => (util[“is” + type] = (value) => typeof value === type.toLowerCase())

);

util.addMethod = addMethod;

[“Object”, “Array”, “RegExp”].forEach(

(type) =>

(util[“isDeep” + type] = (value) =>

_toString.call(value).slice(8, -1).toLowerCase() === type.toLowerCase())

);

util.isShallowObject = (value) =>

typeof value === “object” && !util.isNull(value);

util[“ewObjToArray”] = (value) =>

util.isShallowObject(value) ? Array.prototype.slice.call(value) : value;

util.isNull = (value) => value === null;

util.ewAssign = function (target) {

if (util.isNull(target)) return;

const _ = Object(target);

for (let j = 1, len = arguments.length; j < len; j += 1) {

const source = arguments[j];

if (source) {

for (let key in source) {

if (Object.prototype.hasOwnProperty.call(source, key)) {

_[key] = source[key];

}

}

}

}

return _;

};

util.addClass = (el, className) => el.classList.add(className);

util.removeClass = (el, className) => el.classList.remove(className);

util.hasClass = (el, className) => {

let _hasClass = (value) =>

new RegExp(" " + el.className + " “).test(” " + value + " ");

if (util.isDeepArray(className)) {

return className.some((name) => _hasClass(name));

} else {

return _hasClass(className);

}

};

util[“setCss”] = (el, prop, value) => el.style.setProperty(prop, value);

util.setSomeCss = (el, propValue = []) => {

if (propValue.length) {

propValue.forEach(§ => util.setCss(el, p.prop, p.value));

}

};

util.isDom = (el) =>

util.isShallowObject(HTMLElement)

? el instanceof HTMLElement

: (el &&

util.isShallowObject(el) &&

el.nodeType === 1 &&

util.isString(el.nodeName)) ||

el instanceof HTMLCollection ||

el instanceof NodeList;

util.ewError = (value) =>

console.error(“[ewColorPicker warn]\n” + new Error(value));

util.ewWarn = (value) => console.warn(“[ewColorPicker warn]\n” + value);

util.deepCloneObjByJSON = (obj) => JSON.parse(JSON.stringify(obj));

util.deepCloneObjByRecursion = function f(obj) {

if (!util.isShallowObject(obj)) return;

let cloneObj = util.isDeepArray(obj) ? [] : {};

for (let k in obj) {

cloneObj[k] = util.isShallowObject(obj[k]) ? f(obj[k]) : obj[k];

}

return cloneObj;

};

util.getCss = (el, prop) => window.getComputedStyle(el, null)[prop];

util.$ = (ident) => {

if (!ident) return null;

return document[

ident.indexOf(“#”) > -1 ? “querySelector” : “querySelectorAll”

](ident);

};

util[“on”] = (element, type, handler, useCapture = false) => {

if (element && type && handler) {

element.addEventListener(type, handler, useCapture);

}

};

util[“off”] = (element, type, handler, useCapture = false) => {

if (element && type && handler) {

element.removeEventListener(type, handler, useCapture);

}

};

util[“getRect”] = (el) => el.getBoundingClientRect();

util[“baseClickOutSide”] = (element, isUnbind = true, callback) => {

const mouseHandler = (event) => {

const rect = util.getRect(element);

const target = event.target;

if (!target) return;

const targetRect = util.getRect(target);

if (

targetRect.x >= rect.x &&

targetRect.y >= rect.y &&

targetRect.width <= rect.width &&

targetRect.height <= rect.height

)

return;

if (util.isFunction(callback)) callback();

if (isUnbind) {

// 延迟解除绑定

setTimeout(() => {

util.off(document, util.eventType[0], mouseHandler);

}, 0);

}

};

util.on(document, util.eventType[0], mouseHandler);

};

util[“clickOutSide”] = (context, config, callback) => {

const mouseHandler = (event) => {

const rect = util.getRect(context.$Dom.picker);

let boxRect = null;

if (config.hasBox) {

boxRect = util.getRect(context.$Dom.box);

}

const target = event.target;

if (!target) return;

const targetRect = util.getRect(target);

// 利用rect来判断用户点击的地方是否在颜色选择器面板区域之内

if (config.hasBox) {

if (

targetRect.x >= rect.x &&

targetRect.y >= rect.y &&

targetRect.width <= rect.width

)

return;

// 如果点击的是盒子元素

if (

targetRect.x >= boxRect.x &&

targetRect.y >= boxRect.y &&

targetRect.width <= boxRect.width &&

targetRect.height <= boxRect.height

)

return;

callback();

} else {

if (

targetRect.x >= rect.x &&

targetRect.y >= rect.y &&

targetRect.width <= rect.width &&

targetRect.height <= rect.height

)

return;

callback();

}

setTimeout(() => {

util.off(document, util.eventType[0], mouseHandler);

}, 0);

};

util.on(document, util.eventType[0], mouseHandler);

};

util[“createUUID”] = () =>

(Math.random() * 10000000).toString(16).substr(0, 4) +

“-” +

new Date().getTime() +

“-” +

Math.random().toString().substr(2, 5);

util.removeAllSpace = (value) => value.replace(/\s+/g, “”);

util.isJQDom = (dom) =>

typeof window.jQuery !== “undefined” && dom instanceof jQuery;

//the event

util.eventType = navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)

? [“touchstart”, “touchmove”, “touchend”]

: [“mousedown”, “mousemove”, “mouseup”];

动画函数的封装


const animation = {};

function TimerManager() {

this.timers = [];

this.args = [];

this.isTimerRun = false;

}

TimerManager.makeTimerManage = function (element) {

const elementTimerManage = element.TimerManage;

if (!elementTimerManage || elementTimerManage.constructor !== TimerManager) {

element.TimerManage = new TimerManager();

}

};

const methods = [

{

method: “add”,

func: function (timer, args) {

this.timers.push(timer);

this.args.push(args);

this.timerRun();

},

},

{

method: “timerRun”,

func: function () {

if (!this.isTimerRun) {

let timer = this.timers.shift(),

args = this.args.shift();

if (timer && args) {

this.isTimerRun = true;

timer(args[0], args[1]);

}

}

},

},

{

method: “next”,

func: function () {

this.isTimerRun = false;

this.timerRun();

},

},

];

methods.forEach((method) =>

util.addMethod(TimerManager, method.method, method.func)

);

function runNext(element) {

const elementTimerManage = element.TimerManage;

if (elementTimerManage && elementTimerManage.constructor === TimerManager) {

elementTimerManage.next();

}

}

function registerMethods(type, element, time) {

let transition = “”;

if (type.indexOf(“slide”) > -1) {

transition = “height” + time + " ms";

util.setCss(element, “overflow”, “hidden”);

upAndDown();

} else {

transition = “opacity” + time + " ms";

inAndOut();

}

util.setCss(element, “transition”, transition);

function upAndDown() {

const isDown = type.toLowerCase().indexOf(“down”) > -1;

if (isDown) util.setCss(element, “display”, “block”);

const getPropValue = function (item, prop) {

let v = util.getCss(item, prop);

return util.removeAllSpace(v).length ? parseInt(v) : Number(v);

};

const elementChildHeight = [].reduce.call(

element.children,

(res, item) => {

res +=

item.offsetHeight +

getPropValue(item, “margin-top”) +

getPropValue(item, “margin-bottom”);

return res;

},

0

);

let totalHeight = Math.max(element.offsetHeight, elementChildHeight + 10);

let currentHeight = isDown ? 0 : totalHeight;

let unit = totalHeight / (time / 10);

if (isDown) util.setCss(element, “height”, “0px”);

let timer = setInterval(() => {

currentHeight = isDown ? currentHeight + unit : currentHeight - unit;

util.setCss(element, “height”, currentHeight + “px”);

if (currentHeight >= totalHeight || currentHeight <= 0) {

clearInterval(timer);

util.setCss(element, “height”, totalHeight + “px”);

runNext(element);

}

if (!isDown && currentHeight <= 0) {

util.setCss(element, “display”, “none”);

util.setCss(element, “height”, “0”);

}

}, 10);

}

function inAndOut() {

const isIn = type.toLowerCase().indexOf(“in”) > -1;

let timer = null;

let unit = (1 * 100) / (time / 10);

let curAlpha = isIn ? 0 : 100;

util.setSomeCss(element, [

{

prop: “display”,

value: isIn ? “none” : “block”,

},

{

prop: “opacity”,

value: isIn ? 0 : 1,

},

]);

let handleFade = function () {

curAlpha = isIn ? curAlpha + unit : curAlpha - unit;

if (element.style.display === “none” && isIn)

util.setCss(element, “display”, “block”);

util.setCss(element, “opacity”, (curAlpha / 100).toFixed(2));

if (curAlpha >= 100 || curAlpha <= 0) {

if (timer) clearTimeout(timer);

runNext(element);

if (curAlpha <= 0) util.setCss(element, “display”, “none”);

util.setCss(element, “opacity”, curAlpha >= 100 ? 1 : 0);

} else {

timer = setTimeout(handleFade, 10);

}

};

handleFade();

}

}

[“slideUp”, “slideDown”, “fadeIn”, “fadeOut”].forEach((method) => {

animation[method] = function (element) {

TimerManager.makeTimerManage(element);

element.TimerManage.add(function (element, time) {

return registerMethods(method, element, time);

}, arguments);

};

});

一些颜色操作的算法


const colorRegExp = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;

// RGB color

const colorRegRGB =

/[rR][gG][Bb][Aa]?({2}[\s](2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),?[\s](0.\d{1,2}|1|0)?[)]{1}/g;

// RGBA color

const colorRegRGBA =

/1[gG][Bb][Aa]({3}[\s](1|1.0|0|0?.[0-9]{1,2})[\s][)]{1}$/;

// hsl color

const colorRegHSL =

/2[Ss][Ll](([\s]((100|[0-9][0-9]?)%|0)[\s],)([\s]((100|[0-9][0-9]?)%|0)[\s])[)]$/;

// HSLA color

const colorRegHSLA =

/3[Ss][Ll][Aa](([\s]((100|[0-9][0-9]?)%|0)[\s],){2}([\s](1|1.0|0|0?.[0-9]{1,2})[\s])[)]$/;

/**

* hex to rgba

* @param {*} hex

* @param {*} alpha

*/

function colorHexToRgba(hex, alpha) {

let a = alpha || 1,

hColor = hex.toLowerCase(),

hLen = hex.length,

rgbaColor = [];

if (hex && colorRegExp.test(hColor)) {

//the hex length may be 4 or 7,contained the symbol of #

if (hLen === 4) {

let hSixColor = “#”;

for (let i = 1; i < hLen; i++) {

let sColor = hColor.slice(i, i + 1);

hSixColor += sColor.concat(sColor);

}

hColor = hSixColor;

}

for (let j = 1, len = hColor.length; j < len; j += 2) {

rgbaColor.push(parseInt(“0X” + hColor.slice(j, j + 2), 16));

}

return util.removeAllSpace(“rgba(” + rgbaColor.join(“,”) + “,” + a + “)”);

} else {

return util.removeAllSpace(hColor);

}

}

/**

* rgba to hex

* @param {*} rgba

*/

function colorRgbaToHex(rgba) {

const hexObject = { 10: “A”, 11: “B”, 12: “C”, 13: “D”, 14: “E”, 15: “F” },

hexColor = function (value) {

value = Math.min(Math.round(value), 255);

const high = Math.floor(value / 16),

low = value % 16;

return “” + (hexObject[high] || high) + (hexObject[low] || low);

};

const value = “#”;

if (/rgba?/.test(rgba)) {

let values = rgba

.replace(/rgba?(/, “”)

.replace(/)/, “”)

.replace(/[\s+]/g, “”)

.split(“,”),

color = “”;

values.map((value, index) => {

if (index <= 2) {

color += hexColor(value);

}

});

return util.removeAllSpace(value + color);

}

}

/**

* hsva to rgba

* @param {*} hsva

* @param {*} alpha

*/

function colorHsvaToRgba(hsva, alpha) {

let r,

g,

b,

a = hsva.a; //rgba(r,g,b,a)

let h = hsva.h,

s = (hsva.s * 255) / 100,

v = (hsva.v * 255) / 100; //hsv(h,s,v)

if (s === 0) {

r = g = b = v;

} else {

let t = v,

p = ((255 - s) * v) / 255,

q = ((t - p) * (h % 60)) / 60;

if (h === 360) {

r = t;

g = b = 0;

} else if (h < 60) {

r = t;

g = p + q;

b = p;

} else if (h < 120) {

r = t - q;

g = t;

b = p;

} else if (h < 180) {

r = p;

g = t;

b = p + q;

} else if (h < 240) {

r = p;

g = t - q;

b = t;

} else if (h < 300) {

r = p + q;

g = p;

b = t;

} else if (h < 360) {

r = t;

g = p;

b = t - q;

} else {

r = g = b = 0;

}

}

if (alpha >= 0 || alpha <= 1) a = alpha;

return util.removeAllSpace(

“rgba(” +

Math.ceil® +

“,” +

Math.ceil(g) +

“,” +

Math.ceil(b) +

“,” +

a +

“)”

);

}

/**

* hsla to rgba

* 换算公式:https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4#%E4%BB%8EHSL%E5%88%B0RGB%E7%9A%84%E8%BD%AC%E6%8D%A2

* @param {*} hsla

*/

function colorHslaToRgba(hsla) {

let h = hsla.h,

s = hsla.s / 100,

l = hsla.l / 100,

a = hsla.a;

let r, g, b;

if (s === 0) {

r = g = b = l;

} else {

let compareRGB = (p, q, t) => {

if (t > 1) t = t - 1;

if (t < 0) t = t + 1;

if (t < 1 / 6) return p + (q - p) * 6 * t;

if (t < 1 / 2) return q;

if (t < 2 / 3) return p + (q - p) * 6 * (2 / 3 - t);

return p;

};

let q = l >= 0.5 ? l + s - l * s : l * (1 + s),

p = 2 * l - q,

k = h / 360;

r = compareRGB(p, q, k + 1 / 3);

g = compareRGB(p, q, k);

b = compareRGB(p, q, k - 1 / 3);

}

return util.removeAllSpace(

`rgba( M a t h . c e i l ( r   ∗   255 ) , {Math.ceil(r * 255)}, Math.ceil(r  255),{Math.ceil(g * 255)},${Math.ceil(

b * 255

)},${a})`

);

}

/**

* rgba to hsla

* 换算公式:https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4#%E4%BB%8EHSL%E5%88%B0RGB%E7%9A%84%E8%BD%AC%E6%8D%A2

* @param {*} rgba

*/

function colorRgbaToHsla(rgba) {

const rgbaArr = rgba

.slice(rgba.indexOf(“(”) + 1, rgba.lastIndexOf(“)”))

.split(“,”);

let a = rgbaArr.length < 4 ? 1 : Number(rgbaArr[3]);

let r = parseInt(rgbaArr[0]) / 255,

g = parseInt(rgbaArr[1]) / 255,

b = parseInt(rgbaArr[2]) / 255;

let max = Math.max(r, g, b),

min = Math.min(r, g, b);

let h,

s,

l = (max + min) / 2;

if (max === min) {

h = s = 0;

} else {

const d = max - min;

s = l > 0.5 ? d / (2 - max - min) : d / (max + min);

switch (max) {

case r:

h = (g - b) / d + (g >= b ? 0 : 6);

break;

case g:

h = (b - r) / d + 2;

break;

case b:

h = (r - g) / d + 4;

break;

}

}

return {

colorStr: util.removeAllSpace(

“hsla(” +

Math.ceil(h * 60) +

“,” +

Math.ceil(s * 100) +

“%,” +

Math.ceil(l * 100) +

“%,” +

a +

“)”

),

colorObj: {

h,

s,

l,

a,

},

};

}

/**

* rgba to hsva

* @param {*} rgba

*/

function colorRgbaToHsva(rgba) {

const rgbaArr = rgba

.slice(rgba.indexOf(“(”) + 1, rgba.lastIndexOf(“)”))

.split(“,”);

let a = rgbaArr.length < 4 ? 1 : Number(rgbaArr[3]);

let r = parseInt(rgbaArr[0]) / 255,

g = parseInt(rgbaArr[1]) / 255,

b = parseInt(rgbaArr[2]) / 255;

let h, s, v;

let min = Math.min(r, g, b);

let max = (v = Math.max(r, g, b));

let diff = max - min;

if (max === 0) {

s = 0;

} else {

s = 1 - min / max;

}

if (max === min) {

h = 0;

} else {

switch (max) {

case r:

h = (g - b) / diff + (g < b ? 6 : 0);

break;

case g:

h = 2.0 + (b - r) / diff;

break;

case b:

h = 4.0 + (r - g) / diff;

break;

}

h = h * 60;

}

s = s * 100;

v = v * 100;

return {

h,

s,

v,

a,

};

}

/*

* 任意色值(甚至是CSS颜色关键字)转换为RGBA颜色的方法

* 此方法IE9+浏览器支持,基于DOM特性实现

* @param {*} color

*/

function colorToRgba(color) {

const div = document.createElement(“div”);

util.setCss(div, “background-color”, color);

document.body.appendChild(div);

const c = util.getCss(div, “background-color”);

document.body.removeChild(div);

let isAlpha = c.match(/,/g) && c.match(/,/g).length > 2;

let result = isAlpha

? c

: c.slice(0, 2) + “ba” + c.slice(3, c.length - 1) + “, 1)”;

return util.removeAllSpace(result);

}

/**

* 判断是否是合格的颜色值

* @param {*} color

*/

function isValidColor(color) {

// https://developer.mozilla.org/zh-CN/docs/Web/CSS/color_value#%E8%89%B2%E5%BD%A9%E5%85%B3%E9%94%AE%E5%AD%97

let isTransparent = color === “transparent”;

return (

colorRegExp.test(color) ||

colorRegRGB.test(color) ||

colorRegRGBA.test(color) ||

colorRegHSL.test(color) ||

colorRegHSLA.test(color) ||

(colorToRgba(color) !== “rgba(0,0,0,0)” && !isTransparent) ||

isTransparent

);

}

/**

* @param {*} color

* @returns

*/

function isAlphaColor(color) {

return (

colorRegRGB.test(color) ||

colorRegRGBA.test(color) ||

colorRegHSL.test(color) ||

colorRegHSLA.test(color)

);

}

工具方法这些我们已经完成了,接下来就是正式完成我们的主线功能逻辑了。

构造函数的定义


首先当然是完成我们的构造函数呢,我们把一个颜色选择器看做是一个构造实例,也因此,我们创建一个构造函数。

function ewColorPicker(options){

//主要逻辑

}

好的,接下来,让我们完成第一步,校验用户传入的参数,我们分为2种情况,第一种是如果用户传入的是一个DOM元素字符串或者是一个DOM元素,那么我们就要定义一个默认的配置对象,如果用户传入的是一个自定义的对象,那么我们将不采取默认对象。在校验之前,我们先思考一下可能需要处理的错误情况,也就是说假如用户传入的参数不符合规则,我们是不是需要返回一些错误提示给用户知道,现在让我们来定义一下这些错误规则吧。如下所示:

const NOT_DOM_ELEMENTS = [‘html’,‘head’,‘meta’,‘title’,‘link’,‘style’,‘script’,‘body’];

const ERROR_VARIABLE = {

DOM_OBJECT_ERROR:‘can not find the element by el property,make sure to pass a correct value!’,

DOM_ERROR:‘can not find the element,make sure to pass a correct param!’,

CONFIG_SIZE_ERROR:‘the value must be a string which is one of the normal,medium,small,mini,or must be an object and need to contain width or height property!’,

DOM_NOT_ERROR:‘Do not pass these elements: ’ + NOT_DOM_ELEMENTS.join(’,‘) + ’ as a param,pass the correct element such as div!’,

PREDEFINE_COLOR_ERROR:‘“predefineColor” is a array that is need to contain color value!’,

CONSTRUCTOR_ERROR:‘ewColorPicker is a constructor and should be called with the new keyword!’,

DEFAULT_COLOR_ERROR:‘the “defaultColor” is not an invalid color,make sure to use the correct color!’

};

这些校验错误都是常量,不允许被修改的,所以我们用大写字母来表示。接下来我们就需要在构造函数里做一个校验了。

配置属性的定义与校验


1.校验是否是实例化

判断new.target就可以了,如下所示:

if(util.isUndefined(new.target))return ewError(ERROR_VARIABLE.CONSTRUCTOR_ERROR);

2.定义一个函数startInit,在这个函数里对具体的属性做判断。如下所示:

function startInit(context,options){

let initOptions = initConfig(config);

if(!initOptions)return;

// 缓存配置对象属性

context.config = initOptions.config;

//定义私有属性

context._private = {

boxSize: {

b_width: null,

b_height: null

},

pickerFlag: false,

colorValue: “”,

};

// 在初始化之前所作的操作

context.beforeInit(initOptions.element,initOptions.config,initOptions.error);

}

接下来,我们来看initConfig函数,如下所示:

export function initConfig(config){

// 默认的配置对象属性

const defaultConfig = { …colorPickerConfig };

let element,error,mergeConfig = null;

//如果第二个参数传的是字符串,或DOM对象,则初始化默认的配置

if (util.isString(config) || util.isDom(config) || util.isJQDom(config)) {

mergeConfig = defaultConfig;

element = util.isJQDom(config) ? config.get(0) : config;

error = ERROR_VARIABLE.DOM_ERROR;

} //如果是对象,则自定义配置,自定义配置选项如下:

else if (util.isDeepObject(config) && (util.isString(config.el) || util.isDom(config.el) || util.isJQDom(config.el))) {

mergeConfig = util.ewAssign(defaultConfig, config);

element = util.isJQDom(config.el) ? config.el.get(0) : config.el;

error = ERROR_VARIABLE.DOM_OBJECT_ERROR;

} else {

if(util.isDeepObject(config)){

error = ERROR_VARIABLE.DOM_OBJECT_ERROR;

}else{

error = ERROR_VARIABLE.DOM_ERROR;

}

}

return {

element,

config:mergeConfig,

error

}

}

然后我们来看看默认的配置对象属性:

export const emptyFun = function () { };

const baseDefaultConfig = {

alpha: false,

size: “normal”,

predefineColor: [],

disabled: false,

defaultColor: “”,

pickerAnimation: “height”,

pickerAnimationTime:200,

sure: emptyFun,

clear: emptyFun,

togglePicker: emptyFun,

changeColor: emptyFun,

isClickOutside: true,

}

接下来,我们来看beforeInit函数,如下所示:

function beforeInit(element, config, errorText) {

let ele = util.isDom(element) ? element : util.isString(element) ? util.$(element) : util.isJQDom(element) ? element.get(0) : null;

if (!ele) return util.ewError(errorText);

ele = ele.length ? ele[0] : ele;

if (!ele.tagName) return util.ewError(errorText);

if (!isNotDom(ele)) {

if(!this._color_picker_uid){

this._color_picker_uid = util.createUUID();

}

this.init(ele, config);

}

}

其中,isNotDom方法,我们先定义好:

const isNotDom = ele => {

if (NOT_DOM_ELEMENTS.indexOf(ele.tagName.toLowerCase()) > -1) {

util.ewError(ERROR_VARIABLE.DOM_NOT_ERROR);

return true;

}

return false;

}

最后,我们来看init函数,如下所示:

function init(element, config) {

let b_width, b_height;

//自定义颜色选择器的类型

if (util.isString(config.size)) {

switch (config.size) {

case ‘normal’:

b_width = b_height = ‘40px’;

break;

case ‘medium’:

b_width = b_height = ‘36px’;

break;

case ‘small’:

b_width = b_height = ‘32px’;

break;

case ‘mini’:

b_width = b_height = ‘28px’;

break;

default:

b_width = b_height = ‘40px’;

break;

}

} else if (util.isDeepObject(config.size)) {

b_width = config.size.width && (util.isNumber(config.size.width) || util.isString(config.size.width)) ? (parseInt(config.size.width) <= 25 ? 25 :  parseInt(config.size.width))+ ‘px’ : ‘40px’;

b_height = config.size.height && (util.isNumber(config.size.height) || util.isString(config.size.height)) ? (parseInt(config.size.height) <= 25 ? 25 : parseInt(config.size.height)) + ‘px’ : ‘40px’;

} else {

return util.ewError(ERROR_VARIABLE.CONFIG_SIZE_ERROR);

}

this._private.boxSize.b_width = b_width;

this._private.boxSize.b_height = b_height;

//渲染选择器

this.render(element, config);

}

如此一来,我们的初始化的工作才算是完成,回顾一下,我们在初始化的时候做了哪些操作。我总结如下:

  • 定义了一些错误的常量,用于提示。

  • 验证用户传入的参数,分为2种情况,第一种是字符串或者DOM元素,第二种是自定义对象,其中必须指定el属性为一个DOM元素。

  • 定义了默认配置对象,定义了一些私有变量。

  • 对色块盒子的大小做了一次规范化。

接下来,就是我们实际渲染一个颜色选择器的渲染函数,即render函数。

render函数

render函数的核心思路非常的简单,实际上就是创建一堆元素,然后添加到元素当中去。只不过我们需要注意几点,例如预定义颜色数组,默认颜色值,以及色块盒子的大小,还有就是alpha柱的显隐。如下所示:

ewColorPicker.prototype.render = function(element,config){

let predefineColorHTML = ‘’,

alphaBar = ‘’,

hueBar = ‘’,

predefineHTML = ‘’,

boxDisabledClassName = ‘’,

boxBackground = ‘’,

boxHTML = ‘’,

clearHTML = ‘’,

sureHTML = ‘’,

inputHTML = ‘’,

btnGroupHTML = ‘’,

dropHTML = ‘’,

openChangeColorModeHTML = ‘’,

openChangeColorModeLabelHTML = ‘’,

horizontalSliderHTML = ‘’,

verticalSliderHTML = ‘’;

const p_c = config.predefineColor;

if (!util.isDeepArray(p_c)) return util.ewError(ERROR_VARIABLE.PREDEFINE_COLOR_ERROR);

if (p_c.length) {

p_c.map((color,index) => {

let isValidColorString = util.isString(color) && isValidColor(color);

let isValidColorObj = util.isDeepObject(color) && color.hasOwnProperty(‘color’) && isValidColor(color.color);

let renderColor = isValidColorString ? color : isValidColorObj ? color.color : ‘’;

let renderDisabled = isValidColorObj ? setPredefineDisabled(color.disabled) : ‘’;

predefineColorHTML += `

`;

})

};

//打开颜色选择器的方框

const colorBox = config.defaultColor ? `

` : `
×
`;

//透明度

if (config.alpha) {

alphaBar = `

`;

}

// hue

if (config.hue) {

hueBar = <div class="ew-color-slider-bar"><div class="ew-color-slider-thumb"></div></div>;

}

if (predefineColorHTML) {

predefineHTML = <div class="ew-pre-define-color-container">${predefineColorHTML}</div>;

}

if (config.disabled || config.boxDisabled) boxDisabledClassName = ‘ew-color-picker-box-disabled’;

if (config.defaultColor){

if(!isValidColor(config.defaultColor)){

return util.ewError(ERROR_VARIABLE.DEFAULT_COLOR_ERROR)

}else{

config.defaultColor = colorToRgba(config.defaultColor);

}

};

this._private.color = config.defaultColor;

if (!config.disabled && this._private.color) boxBackground = background:${this._private.color};

// 盒子样式

const boxStyle = width:${this._private.boxSize.b_width};height:${this._private.boxSize.b_height};${boxBackground};

if (config.hasBox) {

boxHTML = <div class="ew-color-picker-box ${boxDisabledClassName}" tabIndex="0" style="${boxStyle}">${colorBox}</div>;

}

if (config.hasClear) {

clearHTML = <button class="ew-color-clear ew-color-drop-btn">${ config.clearText }</button>;

}

if (config.hasSure) {

sureHTML = <button class="ew-color-sure ew-color-drop-btn">${ config.sureText }</button>;

}

if (config.hasClear || config.hasSure) {

btnGroupHTML = <div class="ew-color-drop-btn-group">${clearHTML}${sureHTML}</div>;

文末

js前端的重头戏,值得花大部分时间学习。

JavaScript知识

推荐通过书籍学习,《 JavaScript 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。

前端电子书

另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。

学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。

面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。

这是288页的前端面试题

288页面试题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

` : `
×
`;

//透明度

if (config.alpha) {

alphaBar = `

`;

}

// hue

if (config.hue) {

hueBar = <div class="ew-color-slider-bar"><div class="ew-color-slider-thumb"></div></div>;

}

if (predefineColorHTML) {

predefineHTML = <div class="ew-pre-define-color-container">${predefineColorHTML}</div>;

}

if (config.disabled || config.boxDisabled) boxDisabledClassName = ‘ew-color-picker-box-disabled’;

if (config.defaultColor){

if(!isValidColor(config.defaultColor)){

return util.ewError(ERROR_VARIABLE.DEFAULT_COLOR_ERROR)

}else{

config.defaultColor = colorToRgba(config.defaultColor);

}

};

this._private.color = config.defaultColor;

if (!config.disabled && this._private.color) boxBackground = background:${this._private.color};

// 盒子样式

const boxStyle = width:${this._private.boxSize.b_width};height:${this._private.boxSize.b_height};${boxBackground};

if (config.hasBox) {

boxHTML = <div class="ew-color-picker-box ${boxDisabledClassName}" tabIndex="0" style="${boxStyle}">${colorBox}</div>;

}

if (config.hasClear) {

clearHTML = <button class="ew-color-clear ew-color-drop-btn">${ config.clearText }</button>;

}

if (config.hasSure) {

sureHTML = <button class="ew-color-sure ew-color-drop-btn">${ config.sureText }</button>;

}

if (config.hasClear || config.hasSure) {

btnGroupHTML = <div class="ew-color-drop-btn-group">${clearHTML}${sureHTML}</div>;

文末

js前端的重头戏,值得花大部分时间学习。

JavaScript知识

推荐通过书籍学习,《 JavaScript 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。

前端电子书

另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。

学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。

面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。

这是288页的前端面试题

288页面试题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-9OrGLU6Z-1713229921010)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!


  1. rR ↩︎

  2. hH ↩︎

  3. hH ↩︎

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您介绍一下如何使用HTML、CSS、JavaScript和jQuery来实现一个伸缩导航栏。 首先,我们需要在HTML文件中创建一个导航栏的结构。这可以通过一个无序列表(ul)和一些列表项(li)来实现。每个列表项都应该包含一个链接,以便我们可以在导航栏中添加不同的页面链接。例如: ```html <nav> <ul class="menu"> <li><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Services</a></li> <li><a href="#">Contact</a></li> </ul> </nav> ``` 接下来,我们需要使用CSS来设置导航栏的样式。我们可以使用CSS的层级选择器来选择导航栏中的列表项,并为其设置样式。例如: ```css nav ul.menu { list-style: none; margin: 0; padding: 0; background-color: #333; } nav ul.menu li { display: inline-block; } nav ul.menu li a { display: block; color: #fff; padding: 10px; text-decoration: none; } ``` 现在,我们可以使用jQuery来实现导航栏的伸缩效果。我们可以使用jQuery的文档就绪函数来确保页面加载后我们的代码可以正常运行。然后,我们可以使用jQuery的动画函数来创建一个简单的滑动效果。例如: ```javascript $(document).ready(function() { $('nav ul.menu').hide(); $('nav').hover(function() { $('ul.menu').slideDown('fast'); }, function() { $('ul.menu').slideUp('fast'); }); }); ``` 以上代码中,我们首先使用了`$('nav ul.menu').hide();`来将导航栏隐藏起来。然后,我们使用了`$('nav').hover(function() {...}, function() {...});`来为导航栏添加鼠标悬停事件。当鼠标悬停在导航栏上时,我们使用了`$('ul.menu').slideDown('fast');`来显示导航栏。当鼠标移开时,我们使用了`$('ul.menu').slideUp('fast');`来隐藏导航栏。 现在,您就可以在您的网站上使用这个伸缩导航栏了!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值