先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
正文
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-radius
为50%
即可以将一个元素变成圆。接下来就是阴影部分,这样就实现了我们的小圆圈。当然我们不一定非要实现这样的效果,但是为了还原颜色选择器本身,也方便后续的计算,所以我们还是采用原本的样式。
色阶柱
接下来,我们来看一下色阶柱也就是色调柱的实现。看到这个图,我们应该可以很清晰的分出色阶柱包含了 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 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。
另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。
学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。
面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。
这是288页的前端面试题
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事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 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。
另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。
学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。
面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。
这是288页的前端面试题
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-9OrGLU6Z-1713229921010)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!