HTML5 Canvas CurvyTip
CurvyTip HTML5 Canvas 实验。
引言
这是一份快速指南,介绍如何仅使用Canvas
元素构建一个带有选择选项的弯曲形状的工具提示。 其外观在其他同类小部件中独树一帜。
背景
要继续学习本教程,您需要对 HTML5/Canvas 和 JavaScript 技术有所了解。
该实现的现场示例可以在这里找到:演示。
开始
首先,我们需要创建一个 Canvas
元素,我们将在其上进行绘制
var element = document.createElement("canvas");
然后我们将 canvas 追加到 body 中
var tbody = document.body;
tbody.appendChild(element);
在 Canvas 上绘制
- 我们基本上做的是绘制三个弯曲的形状(我们可以绘制更多,但我喜欢三个),为了绘制每一个,我提出了自己的函数,它计算了绘制线条的不同路径点,然后通过使用
quadraticCurveTo
方法绘制形状(请注意,这可能会很容易改进,但我必须诚实地说,我从来没有在数学/几何方面做得那么好)
function drawSegment(instance, segmentIndex) {
var radius = instance.radius;
var arcRadius = instance.arcRadius;
var startAngle = GetAngle(instance, segmentIndex, 1);
var endAngle = GetAngle(instance, segmentIndex, 0);
var xCenter = instance.cX;
var yCenter = instance.cY / .5
var fol = parseInt(instance.segments / 2) == segmentIndex;
var context = instance.context;
var middleAngle = (startAngle + endAngle) / 2;
var outterSX = getX(startAngle, radius, xCenter);
var outterSY = getY(startAngle, radius, yCenter);
var innerSX = getX(startAngle, radius - arcRadius, xCenter);
var innerSY = getY(startAngle, radius - arcRadius, yCenter);
var outterEX = getX(endAngle, radius, xCenter);
var outterEY = getY(endAngle, radius, yCenter);
var innerEX = getX(endAngle, radius - arcRadius, xCenter);
var innerEY = getY(endAngle, radius - arcRadius, yCenter);
var outterMX = getX(middleAngle, radius + instance.ctxFactor , xCenter);
var outterMY = getY(middleAngle, radius + instance.ctxFactor , yCenter);
var innerMX = getX(middleAngle, radius - arcRadius + instance.ctxFactor, xCenter);
var innerMBX = getX(middleAngle + 4, radius - arcRadius, xCenter);
var innerMPX = getX(middleAngle - 4, radius - arcRadius, xCenter);
var innerMBY = getY(middleAngle + 4, radius - arcRadius, yCenter);
var innerMPY = getY(middleAngle - 4, radius - arcRadius, yCenter);
var innerMCX = getX(middleAngle, radius - arcRadius + instance.ctxFactor, xCenter);
var innerMCY = getY(middleAngle, radius - arcRadius + instance.ctxFactor, yCenter + 8);
var innerMY = getY(middleAngle, radius - arcRadius + instance.ctxFactor, yCenter);
context.moveTo(outterSX, outterSY);
context.quadraticCurveTo(outterSX, outterSY, innerSX, innerSY);
context.moveTo(innerSX, innerSY);
context.quadraticCurveTo(innerMX, innerMY, innerEX, innerEY);
context.quadraticCurveTo(innerEX, innerEY, outterEX, outterEY);
context.quadraticCurveTo(outterMX, outterMY, outterSX, outterSY);
};
这个函数背后的想法是计算将根据角度坐标定义切片/形状的路径点。
这是在 canvas 上一起绘制这三个形状后的效果
if (fol) {
context.quadraticCurveTo(innerSX, innerSY, innerMPX, innerMPY);
context.lineTo(innerMCX, innerMCY);
context.lineTo(innerMBX, innerMBY);
context.quadraticCurveTo(innerEX, innerEY, innerEX, innerEY);
}
这是在 canvas 上绘制后的效果。
Canvas
加上一些颜色,我喜欢用渐变风格的背景而不是纯色。 为了实现这种外观,我们需要在绘制形状(drawSegment
)之前指定渐变颜色。var gradient = this.context.createLinearGradient(0, 0, 0, this.cY / .75);
gradient.addColorStop(0, "rgb(255, 255, 255)");
gradient.addColorStop(1, this.backColor);
this.context.lineWidth = .5;
this.context.fillStyle = gradient;
this.context.strokeStyle = "black";
if (this.hasShadow) {
this.context.save();
this.context.beginPath();
this.context.shadowColor = this.shadowColor;
this.context.shadowBlur = 5;
this.context.shadowOffsetX = 0;
this.context.shadowOffsetY = 5;
for (var i = 0; i < this.segments; i++) {
drawSegment(this, i);
}
this.context.closePath();
this.context.fill();
this.context.restore();
}
drawImage
函数,但为其添加阴影,以便给它一个非常小的浮雕效果,为了实现这一点,我们需要根据形状的角度位置计算图像的位置。for (var i = 0; i < this.segments; i++) {
var stAngle = GetAngle(this, i, 1);
var edAngle = GetAngle(this, i, 0);
var xCenter = this.cX;
var yCenter = this.cY / .5
var middleAngle = (stAngle + edAngle) / 2;
var outterMX = getX(middleAngle, this.radius - (this.arcRadius / 2), xCenter);
var outterMY = getY(middleAngle, this.radius - (this.arcRadius / 2), yCenter);
if (typeof this.tiles[i] != 'undefined') {
this.context.save();
this.context.shadowColor = 'rgb(0, 0, 0)';
this.context.shadowOffsetX = 0;
this.context.shadowOffsetY = -1;
this.context.shadowBlur = 1;
//Keep image position
this.context.translate(outterMX, outterMY)
this.context.rotate(-this.rotation * TO_RADIANS);
this.context.drawImage(this.tiles[i], -(this.tiles[i].width / 2), -(this.tiles[i].height / 2));
this.context.restore();
}
}
位置操作
- 旋转:旋转 CurvyTip 实际上非常简单,我们需要做的就是旋转 canvas 本身。 为此,我们首先将我们的上下文转换为中心,然后按如下方式旋转:
this.clearCvs();
// Move registration point to the center of the canvas
this.context.save();
this.context.translate(this.cX, this.cY);
// Rotate
this.context.rotate(this.rotation * TO_RADIANS);
// Move registration point back to the top left corner of canvas
this.context.translate(-this.cX, -this.cY);
this.context.scale(this.scale, this.scale);
this.drawCanvas();
this.context.restore();
但我们也希望图像也旋转,否则当 canvas 旋转时图像将被旋转, 如图所示
为了避免这种情况,我们需要在绘制图像时旋转它(在 canvas 旋转之前)
if (typeof this.tiles[i] != 'undefined') {
this.context.save();
this.context.shadowColor = 'rgb(0, 0, 0)';
this.context.shadowOffsetX = 0;
this.context.shadowOffsetY = -1;
this.context.shadowBlur = 1;
//Keep image position
this.context.translate(outterMX, outterMY)
this.context.rotate(-this.rotation * TO_RADIANS);
this.context.drawImage(this.tiles[i], -(this.tiles[i].width / 2), -(this.tiles[i].height / 2));
this.context.restore();
}
function SetPosition(instance) {
var position = instance.anchor;
var padding = 10;
var offset = getOffset(instance.target);
var twidth = instance.target.offsetWidth;
var theight = instance.target.offsetHeight;
var fol = parseInt(instance.segments / 2);
var startAngle = GetAngle(instance, fol, 1);
var endAngle = GetAngle(instance, fol, 0);
var middleAngle = (startAngle + endAngle) / 2;
var yCenter = instance.cY / .5;
var xCenter = instance.cX;
var innerMCY = getY(middleAngle, instance.radius - instance.arcRadius + instance.ctxFactor, yCenter + 8);
var left = 0;
var top = 0;
switch (position) {
case "top":
case null:
default:
left = (offset.left - (instance.canvas.width / 2)) + (instance.target.offsetWidth / 2);
top = offset.top - innerMCY - padding;
break;
case "bottom":
left = (offset.left - (instance.canvas.width / 2)) + (instance.target.offsetWidth / 2);
top = offset.top + theight - (instance.canvas.height - innerMCY) + padding;
break;
case "left":
left = offset.left - padding - innerMCY;
top = offset.top + (theight / 2) - innerMCY;
break;
case "right":
left = offset.left + twidth - (instance.canvas.width - innerMCY) + padding;
top = offset.top + (theight / 2) - innerMCY;
break;
}
}
鼠标交互
即使 canvas 本身没有严格的方式来监听鼠标事件,也有几种方法可以实现这一点。
基本上,我们将创建一个引用每个区域位置的区域,然后使用 isPointInPath
函数将鼠标坐标与区域区域进行比较。
- 这是一个非常粗略的例子,说明了如何实现它
var pos = this.mousePos;
if (pos != null && this.context.isPointInPath(pos.x, pos.y)) {
// handle onclick
if (this.mouseClick && this.currentRegion.onclick !== undefined) {
//mouse click
}
// handle onmouseover
else if (!this.mouseOver && this.currentRegion.onmouseover !== undefined) {
//mouse over
}
}
else {
if (this.currentRegion.onmouseout !== undefined) {
//mouse out
}
}
if (this.hitSegment == i) {
this.context.save();
this.context.beginPath();
drawSegment(this, i);
this.context.fillStyle = this.mouseOverColor;
this.context.fill();
this.context.closePath();
this.context.restore();
}
如何使用它
初始化 curvyTip
var opts = {
alwaysVisible: true, // Always keep canvas visible
images: ['t.png', 'f.png', 'r.png'] // The images path
};
var curvyTip = new CurvyTip('targetElementId'); // Create canvas element
curvyTip.setOptions(opts); // Specify initial settings
捕获事件
var curvyTip = new CurvyTip('targetElementId'); // Create canvas element
//Adding Listeners for supported Events.
curvyTip.addListener("click", function (index) {
//handle event here
});
curvyTip.addListener("mouseover", function (index) {
//handle event here
});
curvyTip.addListener("mouseout", function (index) {
//handle event here
});
//Removing Listeners
curvyTip.removeListener("click");
curvyTip.removeListener("mouseover");
curvyTip.removeListener("mouseout");
摘要
我已经谈论了 CurvyTip(HTML5 Canvas 实验)的一些细节,希望您喜欢这篇文章。
变更
版本 1.1 (2013 年 3 月 18 日)
OnLoad
位置修复,当 CurvyTip 在加载时显示时存在问题。- 稍微重构了代码,使其更灵活。 现在公开了
Gradient
属性,并且可用于覆盖默认的渐变样式。