Maison  >  Article  >  interface Web  >  Poursuivre le plaisir du canevas : créer un plugin de graphique à barres, partie 2

Poursuivre le plaisir du canevas : créer un plugin de graphique à barres, partie 2

王林
王林original
2023-08-30 08:45:10843parcourir

Dans cette série en deux parties, nous combinerons l'élément polyvalent canvas et la puissante bibliothèque jQuery pour créer un plugin de graphique à barres. Dans la deuxième partie, nous le convertirons en plugin jQuery, puis ajouterons du plaisir pour les yeux et d'autres fonctionnalités.

Pour terminer la série en deux parties Joy of Canvas, nous allons créer aujourd'hui un plugin de graphique à barres. Veuillez noter qu'il ne s'agit pas d'un plugin ordinaire ; Nous démontrerons l'amour de jQuery pour l'élément canvas pour créer un plugin très puissant.

Dans la première partie, nous nous concentrons uniquement sur l'implémentation de la logique du plugin en tant que script autonome. A la fin de la première partie, notre histogramme ressemble à ceci.

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

Résultats à la fin de la partie 1

Dans cette dernière partie, nous travaillerons sur la conversion de notre code et en ferons un véritable plugin jQuery, en ajoutant quelques détails visuels et enfin en incluant des fonctionnalités supplémentaires. En fin de compte, notre résultat ressemblera à ceci :

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

Produit fini

Êtes-vous tous réchauffés ? Commençons !


Procédures de plug-in

Avant de commencer à convertir du code en plug-ins, nous devons d'abord comprendre certaines procédures de création de plug-ins.


Plugin de nom

Nous commençons par choisir un nom pour le plugin. J'ai choisi barGraph et j'ai renommé le fichier JavaScript en jquery.barGraph.js. Maintenant, nous incluons tout le code de l’article précédent dans l’extrait de code suivant.

$.fn.barGraph = function(settings) {  
//code here
}

Paramètrescontient tous les paramètres facultatifs transmis au plugin.


Résoudre le problème du signe $

Dans la création de plugins jQuery, il est courant d'envisager d'utiliser jQuery au lieu des alias $ dans votre code pour minimiser les conflits avec d'autres bibliothèques Javascript. Au lieu de subir tous ces problèmes, nous pouvons utiliser des alias personnalisés comme mentionné dans la documentation jQuery. Nous enveloppons tout le code du plugin dans cette fonction anonyme auto-exécutable comme ceci :

(function($) {
$.fn.barGraph = function(settings) {  
//plugin implementation code here
}
})(jQuery);

Essentiellement, nous enveloppons tout le code dans une fonction et y passons jQuery. Désormais, nous pouvons utiliser librement l'alias $ dans notre code sans craindre qu'il n'entre en conflit avec d'autres bibliothèques JavaScript.


Valeur par défaut

Lors de la conception d'un plugin, c'est une bonne idée d'exposer un nombre raisonnable de paramètres à l'utilisateur, tout en utilisant également des options par défaut raisonnables si l'utilisateur utilise le plugin sans lui transmettre aucune option. Dans cette optique, nous allons permettre à l'utilisateur de modifier chacune des variables d'options graphiques que j'ai mentionnées dans l'article précédent de cette série. Faire cela est simple : il suffit de définir chaque variable comme une propriété de l'objet, puis d'y accéder.

var defaults = {  
	         barSpacing = 20,
	 		 barWidth = 20, 
	    	 cvHeight = 220,
			 numYlabels = 8,
			 xOffset = 20,
			 maxVal, 
			 gWidth=550, 
			 gHeight=200;
           };

Nous devons éventuellement fusionner les options par défaut avec les options passées et hiérarchiser les options passées. Cette ligne gère ce problème.

var option = $.extend(defaults, settings);

N'oubliez pas de modifier les noms des variables si nécessaire. Tel que -

return (param*barWidth)+((param+1)*barSpacing)+xOffset;

...changé en :

return (param*option.barWidth)+((param+1)*option.barSpacing)+option.xOffset;

Refactor

C'est ici que le plugin est finalisé. Notre ancienne implémentation ne pouvait générer qu'un seul graphique dans une page, et la possibilité de créer plusieurs graphiques dans une page était la principale raison pour laquelle nous avons créé un plugin pour cette fonctionnalité. De plus, nous devons nous assurer que les utilisateurs n'ont pas besoin de créer un élément de canevas pour chaque graphique qu'ils souhaitent créer. Dans cet esprit, nous créerons dynamiquement des éléments de canevas selon nos besoins. Allons-y. Nous examinerons les versions antérieures et plus récentes des parties pertinentes du code.


Plugin d'appel

Avant de commencer, je tiens à souligner comment notre plugin sera appelé.

$("#years").barGraph
   ({  
		 barSpacing = 30,
        barWidth = 25,
		 numYlabels = 12,
   });

C'est aussi simple que cela. ans est l'identifiant de la table qui contient toutes nos valeurs. Nous passons les options selon les besoins.


Obtenir la source de données

Tout d’abord, nous devons d’abord référencer la source de données du graphique. Nous accédons maintenant à l'élément source et obtenons son identifiant. Ajoutez les lignes suivantes à l'ensemble de variables graphiques que nous avons déclarées précédemment.

var dataSource = $(this).attr("id");

Nous définissons une nouvelle variable et lui attribuons la valeur de l'attribut ID de l'élément passé. Dans notre code, this fait référence à l'élément DOM actuellement sélectionné. Dans notre exemple, il fait référence au tableau avec l'ID ans.

Dans l'implémentation précédente, l'ID de la source de données était codé en dur. Nous le remplaçons maintenant par l'attribut ID que nous avons extrait précédemment. Une première version de la fonction grabValues ressemblait à ceci :

function grabValues ()
	 {
	 	// Access the required table cell, extract and add its value to the values array.
		 $("#data tr td:nth-child(2)").each(function(){
		 gValues.push($(this).text());
	 	 });
	 
		 // Access the required table cell, extract and add its value to the xLabels array.
		 $("#data tr td:nth-child(1)").each(function(){
	 	xLabels.push($(this).text());
	 	 });
	 }

Mise à jour vers :

function grabValues ()
	 {
     	// Access the required table cell, extract and add its value to the values array.
	 	$("#"+dataSource+" tr td:nth-child(2)").each(function(){
		 gValues.push($(this).text());
	 	 });
	 
		 // Access the required table cell, extract and add its value to the xLabels array.
		 $("#"+dataSource+" tr td:nth-child(1)").each(function(){
	 	xLabels.push($(this).text());
	 	 });
	 }

注入 Canvas 元素

function initCanvas ()
	 {
		 $("#"+dataSource).after("<canvas id=\"bargraph-"+dataSource+"\" class=\"barGraph\"> </canvas>");
		 
         // Try to access the canvas element 
     	cv = $("#bargraph-"+dataSource).get(0);
        
	 	if (!cv.getContext) 
	 	{ return; }
	 
     	// Try to get a 2D context for the canvas and throw an error if unable to
     	ctx = cv.getContext('2d');
	 	if (!ctx) 
	 	{ return; }
	 }

我们创建一个canvas元素并将其注入到表格之后的DOM中,该表格充当数据源。 jQuery 的 after 函数在这里非常方便。还应用了 barGraph 的类属性和 barGraph-dataSourceID 格式的 ID 属性,以使用户能够根据需要将它们全部设置为组或单独设置样式。 p>


循环传递的元素

有两种方法可以实际调用此插件。您可以单独创建每个图表并仅传入一个数据源,也可以传入多个数据源。在后一种情况下,我们当前的构造将遇到错误并退出。为了纠正这个问题,我们使用 each 构造来迭代传递的元素集。

(function($){
	$.fn.barGraph = function(settings) {
	
	// Option variables
	var defaults = {  
	         // options here
           };  
		   
	// Merge the passed parameters with the defaults	   
    var option = $.extend(defaults, settings);  
	
	// Cycle through each passed object
	this.each(function() { 
	
	// Implementation code here
	});
              
	// Returns the jQuery object to allow for chainability.
	return this;
	}
})(jQuery);

我们在获取并合并 this.each 构造中的设置后封装了所有代码。我们还确保最后返回 jQuery 对象以实现可链接性。

至此,我们的重构就完成了。我们应该能够调用我们的插件并根据需要创建尽可能多的图表。


添加养眼效果

现在我们的转换已经完成,我们可以努力使其视觉效果更好。我们将在这里做很多事情。我们将分别研究它们。


主题

旧版本使用温和的灰色来绘制图表。我们现在将为酒吧实施主题机制。这本身由一系列步骤组成。

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

海洋:默认主题 继续 Canvas 的乐趣:构建条形图插件,第 2 部分

树叶 继续 Canvas 的乐趣:构建条形图插件,第 2 部分

樱花 继续 Canvas 的乐趣:构建条形图插件,第 2 部分

频谱

将其添加到选项

var defaults = {  
             // Other defaults here 
	 	 	 theme: "Ocean",
           };

我们在默认设置中添加了一个主题选项,使用户能够将主题更改为四个可用预设中的任何一个。

设置当前选择的主题

function grabValues ()
	 {
	 	// Previous code
		 
		switch(option.theme)
		{
			case 'Ocean':
			gTheme = thBlue;
			break;
			case 'Foliage':
			gTheme = thGreen;
			break;
			case 'Cherry Blossom':
			gTheme = thPink;
			break;
			case 'Spectrum':
			gTheme = thAssorted;
			break;
		} 
	 }

一个简单的switch构造查看option.theme设置并将gTheme变量指向必要的颜色数组。我们对主题使用描述性名称,而不是通用名称。

定义颜色数组

// Themes
	var thPink = ['#FFCCCC','#FFCCCC','#FFC0C0','#FFB5B5','#FFADAD','#FFA4A4','#FF9A9A','#FF8989','#FF6D6D'];
	var thBlue = ['#ACE0FF','#9CDAFF','#90D6FF','#86D2FF','#7FCFFF','#79CDFF','#72CAFF','#6CC8FF','#57C0FF'];
	var thGreen = ['#D1FFA6','#C6FF91','#C0FF86','#BCFF7D','#B6FF72','#B2FF6B','#AAFE5D','#A5FF51','#9FFF46'];
	var thAssorted = ['#FF93C2','#FF93F6','#E193FF','#B893FF','#93A0FF','#93D7FF','#93F6FF','#ABFF93','#FF9B93'];

然后我们定义许多数组,每个数组保存一系列特定颜色的色调。它们从较浅的色调开始并不断增加。稍后我们将循环遍历这些数组。添加主题就像添加您需要的特定颜色的数组一样简单,然后修改之前的开关以反映更改。

辅助函数

function getColour (param)
      {
         return Math.ceil(Math.abs(((gValues.length/2) -param)));
	  }

这是一个很小的函数,可以让我们实现类似渐变的效果并将其应用于图表。本质上,我们计算要渲染的值数量的一半与传递的参数(即数组中当前所选项目的索引)之间的绝对差。这样,我们就能够创建平滑的渐变。由于我们只在每个颜色数组中定义了九种颜色,因此我们的图表仅限于十八个值。扩展这个数字应该是相当微不足道的。

设置fillStyle

function drawGraph ()
	 {
	    for(index=0; index<gValues.length; index++)
	      {
		    ctx.save();
			ctx.fillStyle = gTheme[getColour(index)];
	        ctx.fillRect( x(index), y(gValues[index]), width(), height(gValues[index]));  
		    ctx.restore();
	      }
	 }

这是我们实际为图表设置主题的地方。我们没有为 fillStyle 属性设置静态值,而是使用 getColour 函数来检索当前所选主题数组中元素的必要索引。


不透明度

接下来,我们将让用户能够控制所绘制条形的不透明度。设置过程分为两步。

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

不透明 继续 Canvas 的乐趣:构建条形图插件,第 2 部分

值为 0.8

将其添加到选项

var defaults = {  
            // Other defaults here 
	 	 	 barOpacity : 0.8,
           };

我们在默认值中添加了一个 barOpacity 选项,使用户能够将图形的不透明度更改为 0 到 1 之间的值,其中 0 是完全透明,1 是完全不透明。

设置globalAlpha

function drawGraph ()
	 {
	    for(index=0; index<gValues.length; index++)
	      {
		    ctx.save();
			ctx.fillStyle = gTheme[getColour(index)];
            ctx.globalAlpha = option.barOpacity;
	        ctx.fillRect( x(index), y(gValues[index]), width(), height(gValues[index]));  
		    ctx.restore();
	      }
	 }

globalAlpha 属性控制渲染元素的不透明度或透明度。我们将此属性的值设置为传递的值或默认值以增加一点透明度。作为合理的默认值,我们使用值 0.8 使其稍微透明。


网格

网格对于处理图表中呈现的数据非常有用。虽然我最初想要一个合适的网格,但后来我选择了一系列与 Y 轴标签对齐的水平线,并完全抛弃了垂直线,因为它们只是妨碍了数据。解决了这个问题,让我们来实现一种渲染它的方法。

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

禁用网格 继续 Canvas 的乐趣:构建条形图插件,第 2 部分

启用网格

使用路径和lineTo方法创建线条似乎是绘制图形最明显的解决方案,但我碰巧遇到了一个渲染错误,这使得这种方法不适合。因此,我也坚持使用 fillRect 方法来创建这些线。这是完整的函数。

function drawGrid ()
      {
		  for(index=0; index<option.numYlabels; index++)
	      {
		   ctx.fillStyle = "#AAA";
		   ctx.fillRect( option.xOffset, y(yLabels[index])+3, gWidth, 1);
		  }
      }

这与绘制 Y 轴标签非常相似,只不过我们不是渲染标签,而是绘制一条横跨图形宽度、宽度为 1 px 的水平线。 y 函数帮助我们定位。

将其添加到选项

var defaults = {  
             // Other defaults here 
	 	 	 disableGrid : false,
           };

我们在默认值中添加了一个 disableGrid 选项,使用户能够控制是否渲染网格。默认情况下,它是渲染的。

    // Function calls
    	if(!option.disableGrid) { drawGrid(); }

我们只是检查用户是否希望渲染网格并进行相应操作。


大纲

现在条形图都已着色,在较浅的背景下缺乏强调。为了纠正这个问题,我们需要 1px 的描边。有两种方法可以做到这一点。第一种也是最简单的方法是在 drawGraph 方法中添加一个 strokeRect 方法;或者,我们可以使用 lineTo 方法来快速绘制矩形。我选择了前一条路线,因为像之前一样,lineTo 方法向我抛出了一些奇怪的渲染错误。

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

没有抚摸 继续 Canvas 的乐趣:构建条形图插件,第 2 部分

抚摸

将其添加到选项

首先,我们将其添加到defaults对象中,以便用户控制是否应用它。

var defaults = {  
             // Other defaults here 
	 	 	 showOutline : true,
           };
function drawGraph ()
	 {
	       // Previous code
			if (option.showOutline)
			{
			ctx.fillStyle = "#000";
			ctx.strokeRect( x(index), y(gValues[index]), width(), height(gValues[index]));  
			}
			// Rest of the code
	      }
	 }

我们检查用户是否想要渲染轮廓,如果是,我们继续。这与渲染实际条形几乎相同,只是我们使用 tripleRect 方法而不是使用 fillRect 方法。


阴影

在原始实现中,画布元素本身和条形的实际渲染空间之间没有区别。我们现在就纠正这个问题。

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

无底纹 继续 Canvas 的乐趣:构建条形图插件,第 2 部分

有底纹

function shadeGraphArea ()
      {
	    ctx.fillStyle = "#F2F2F2";
	    ctx.fillRect(option.xOffset, 0, gWidth-option.xOffset, gHeight); 
      }

这是一个很小的函数,可以遮蔽所需区域。我们覆盖画布元素减去两个轴标签覆盖的区域。前两个参数指向起点的x和y坐标,后两个参数指向所需的宽度和高度。从 option.offset 开始,我们消除了 Y 轴标签覆盖的区域,并通过将高度限制为 gHeight,我们消除了 X 轴标签。


添加功能

现在我们的图表看起来足够漂亮了,我们可以集中精力向我们的插件添加一些新功能。我们将分别讨论每一个。

考虑这张著名的 8K 峰值图。

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

当最高值足够高,并且大多数值落在最大值的 10% 以内时,图表就不再有用。我们有两种方法来纠正这个问题。


显示值

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

我们将首先从更简单的解决方案开始。通过将各个图表的值呈现在顶部,实际上解决了问题,因为可以轻松地区分各个值。下面是它的实现方式。

var defaults = {  
             // Other defaults here 
	 	 	 showValue: true,
           };

首先,我们向 defaults 对象添加一个条目,以使用户能够随意打开和关闭它。

    // Function calls
	if(option.showValue) { drawValue(); }

我们检查用户是否希望显示该值并进行相应处理。

function drawValue ()
      {
		  for(index=0; index<gValues.length; index++)
	      {
		      ctx.save();
			  ctx.fillStyle= "#000";
			  ctx.font = "10px 'arial'";
			  var valAsString = gValues[index].toString();
		      var valX = (option.barWidth/2)-(valAsString.length*3);
		      ctx.fillText(gValues[index], x(index)+valX,  y(gValues[index])-4);
			  ctx.restore();
		  }
      }

我们迭代gValues数组并单独渲染每个值。涉及 valAsStringvalX 的计算只不过是帮助我们正确缩进的微小计算,因此它看起来并不不合适。


规模

继续 Canvas 的乐趣:构建条形图插件,第 2 部分

这是两个解决方案中更难的一个。在此方法中,我们不是从 0 开始 Y 轴标签,而是从更接近最小值的位置开始。我们边走边解释。请注意,在上面的示例中,后续值相对于最大值之间的差异非常微不足道,并且没有显示出其有效性。其他数据集应该更容易解析结果。

将其添加到选项

var defaults = {  
             // Other defaults here 
	 	 	 scale: false
           };

更新比例函数

由于scale函数是渲染过程中不可或缺的一部分,因此我们需要更新它以允许缩放功能。我们像这样更新它:

function scale (param)
      {
	   return ((option.scale) ? Math.round(((param-minVal)/(maxVal-minVal))*gHeight) : Math.round((param/maxVal)*gHeight));
      }

我知道这看起来有点复杂,但它看起来只是因为使用了三元条件运算符。本质上,我们检查 option.scale 的值,如果它为 false,则执行旧代码。如果为真,我们现在不会将值标准化为数组中最大值的函数,而是将其标准化为最大值和最小值之差的函数。这让我们想到:

更新maxValues函数

我们现在需要找出最大值和最小值,而不是之前只能找出最大值。函数更新为:

function minmaxValues (arr)
     {
		maxVal=0;
		
	    for(i=0; i<arr.length; i++)
	    {
		 if (maxVal<parseInt(arr[i]))
		 {
		 maxVal=parseInt(arr[i]);
	     } 
	    }
		minVal=maxVal;
		for(i=0; i<arr.length; i++)
	    {
		 if (minVal>parseInt(arr[i]))
		 {
		 minVal=parseInt(arr[i]);
	     }  
		}
	   maxVal*= 1.1;
       minVal = minVal - Math.round((maxVal/10));
	 }

我确信您可以在一个循环中完成相同的任务,而无需使用像我一样多的代码行,但当时我感觉特别没有创造力,所以请耐心等待。完成计算手续后,我们将 maxVal 变量增加 5%,将 minVal 变量增加 5%,减去 minVal 的 5% >maxVal 的 值。这是为了确保条形不会每次都接触顶部,并且每个 Y 轴标签之间的差异是均匀的。

更新drawYlabels函数

完成所有基础工作后,我们现在继续更新 Y 轴标签渲染例程以反映缩放。

function drawYlabels()
      {
		 ctx.save(); 
	     for(index=0; index<option.numYlabels; index++)
	      {
			  if (!option.scale)
			  {
		  		 yLabels.push(Math.round(maxVal/option.numYlabels*(index+1)));
			  }
			  else
			  {
				  var val= minVal+Math.ceil(((maxVal-minVal)/option.numYlabels)*(index+1));
		  		  yLabels.push(Math.ceil(val));  
			  }
		   ctx.fillStyle = option.labelColour;
		   var valAsString = yLabels[index].toString();
		   var lblX = option.xOffset - (valAsString.length*7);
		   ctx.fillText(yLabels[index], lblX, y(yLabels[index])+10);
	      }
		   if (!option.scale)
		   {
	        	ctx.fillText("0", option.xOffset -7, gHeight+7);
		   }
		  else
		  {
		    var valAsString = minVal.toString();
		    var lblX = option.xOffset - (valAsString.length*7);
		    ctx.fillText(minVal, lblX, gHeight+7);  
		  }
		  ctx.restore();
      }

如果你问我的话,更新内容相当丰富!功能的核心保持不变。我们只是检查用户是否启用了扩展并根据需要分支代码。如果启用,我们会更改 Y 标签的分配方式,以确保它们遵循新算法。现在,我们不再将最大值划分为 n 个均匀间隔的数字,而是计算最大值和最小值之间的差值,将其划分为均匀间隔的数字,并将其添加到最小值以构建 Y 轴标签数组。之后,我们照常进行,单独渲染每个标签。由于我们手动渲染了最底部的 0,因此我们必须检查是否启用了缩放,然后在其位置渲染最小值。不要介意每个传递参数的小数字添加;只是为了确保图表的每个元素都按预期排列。


动态调整大小

在我们之前的实现中,我们对图表的维度进行了硬编码,当值的数量发生变化时,这会带来很大的困难。我们现在要纠正这个问题。

将其添加到选项

var defaults = {  
            // Other defaults here 
	 	 	 cvHeight: 250, //In px 
           };

我们让用户单独设置canvas元素的高度。所有其他值都是动态计算的并根据需要应用。

更新initCanvas函数

initCanvas 函数处理所有画布初始化,因此需要更新以实现新功能。

function initCanvas ()
	 {
		 $("#"+dataSource).after("<canvas id=\"bargraph-"+dataSource+"\" class=\"barGraph\"> </canvas>");
		 
	 	// Try to access the canvas element 
     	cv = $("#bargraph-"+dataSource).get(0);
	 	cv.width=gValues.length*(option.barSpacing+option.barWidth)+option.xOffset+option.barSpacing;
		cv.height=option.cvHeight;
		gWidth=cv.width;
		gHeight=option.cvHeight-20;
	 
	 	if (!cv.getContext) 
	 	{ return; }
	 
     	// Try to get a 2D context for the canvas and throw an error if unable to
     	ctx = cv.getContext('2d');
	 	if (!ctx) 
	 	{ return; }
	 }

注入canvas元素后,我们获得了对所创建元素的引用。画布元素的宽度计算为数组中元素数量的函数 - gValues ,每个条之间的空间 - option.barSpacing ,每个条本身的宽度- option.barWidth 和最后option.xOffset。图表的宽度根据每个参数动态变化。高度是用户可修改的,默认为 220 像素,栏本身的渲染区域为 220 像素。 20px 分配给 X 轴标签。


隐藏来源

创建图表后,用户可能希望隐藏源表,这是有道理的。考虑到这一点,我们让用户决定是否删除该表。

var defaults = {  
            // Other defaults here 
			 hideDataSource: true,
           };
	if (option.hideDataSource) { $("#"+dataSource).remove();}

我们检查用户是否想要隐藏表格,如果是,我们使用 jQuery 的 remove 方法将其从 DOM 中完全删除。


优化我们的代码

现在所有的艰苦工作都已经完成,我们可以回顾一下如何优化我们的代码。由于该代码完全是为了教学目的而编写的,因此大部分工作都被封装为单独的函数,而且它们比需要的要冗长得多。

如果您确实想要尽可能精简的代码,我们的整个插件(不包括初始化和计算)可以在两个循环内重写。一个循环遍历 gValues 数组来绘制条形本身和 X 轴标签;第二个循环从 0 迭代到 numYlabels 以渲染网格和 Y 轴标签。代码看起来会更加混乱,但是,它应该会导致代码库明显更小。


摘要

就是这样,伙计们!我们完全从头开始创建了一个高级插件。我们研究了本系列中的许多主题,包括:

  • 查看画布元素的渲染方案。
  • canvas 元素的一些渲染方法。
  • 标准化值使我们能够将其表达为另一个值的函数。
  • 使用 jQuery 的一些有用的数据提取技术。
  • 渲染图表的核心逻辑。
  • 将我们的脚本转换为成熟的 jQuery 插件。
  • 如何增强视觉效果并进一步扩展其功能。

我希望您在读这篇文章时和我在写它时一样享受乐趣。这是一个 270 多行的作品,我确信我遗漏了一些东西。欢迎点击评论并询问我。或者批评我。或者夸奖我。你知道,这是你的决定!快乐编码!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn