Home > Web Front-end > CSS Tutorial > Modular practice of css preprocessing language

Modular practice of css preprocessing language

巴扎黑
Release: 2017-03-29 14:43:26
Original
1341 people have browsed it

Writing CSS is a common and frequent task in front-end work. Since CSS is not a language, the programming seems a bit crude. For small projects, the amount of CSS is not huge and the problem is not highlighted. However, if you want to develop and maintain a larger project, you need to manage and standardize CSS, otherwise irreversible consequences will occur ( Who are you trying to scare? ).

background

In the previous section [Talk about modularity from CSS], we optimized and improved the way CSS is written through normative constraints to form a sustainable development route. But something remains: redundancy. Although we euphemistically share the volume of common by defining public modules and private modules, the volume of common is still too large, and from a design perspective, we should refine as many public modules as possible to better achieve reuse. The most ideal situation is that all modules are stored in a common library, and they can be directly transferred from the library wherever they are needed. This beautiful wish is not unattainable. With the help of preprocessing language, we can easily accomplish this.

The preprocessing language is a CSS-like language. We know that CSS itself is not a language, and the preprocessing language was born to fill this part of the language function. It implements the definition of variables, functions, and mixes, as well as the functions of file reference, merging, and compression, making CSS object-oriented and able to cope with complex and huge businesses.

There are currently two popular preprocessing languages: less and sass. For study, you can get started with both, and for work, try to be familiar with one. I use sass more often, so the following content is introduced using sass as the basic language. The two have many similarities in features, so you don’t have to worry about any huge differences in implementation.

sass

You can learn basic grammar on the official website (English) or w3cplus sass guide (Chinese). We will only go through it briefly here and talk about some of the content we need to use. We will not cover everything.

Sass has two suffix file names: one has the suffix name sass and does not use braces and semicolons; the other is the scss file we use here, which is similar to the css file format we usually write, using curly braces and semicolon. All sass files mentioned in this tutorial refer to files with the suffix scss. It is also recommended to use files with the suffix scss to avoid errors due to the strict format requirements of the sass suffix. ——Excerpted from w3cplus sass guide

1. Nesting (very important feature)

There are two types of nesting in Sass: one is the nesting of selectors; the other is the nesting of attributes. What we usually talk about or use is the nesting of selectors. ——Excerpted from w3cplus sass guide

Selector Nesting The so-called selector nesting refers to nesting another selector within a selector to achieve inheritance, thereby enhancing the structure and readability of the Sass file. In selector nesting, you can use & to represent the parent element selector. ——Excerpted from w3cplus sass guide

// index.scss

.g-index {
  ...

  .g-hd {
    ...

    .m-nav { ... }
  }

  .g-bd {
    ...

    .m-news { ... }
  }

  .g-ft {
    ...

    .m-copy_right { ... }
  }

  .m-dialog {
    display: none;

    &.z-active {  // 留意此处&的用法
      display: block;
    }
  }
}
Copy after login

After compilation:

/* index.css */

.g-index { ... }
.g-index .g-hd { ... }
.g-index .g-hd .m-nav { ... }

.g-index .g-bd { ... }
.g-index .g-bd .m-news { ... }

.g-index .g-ft { ... }
.g-index .g-ft .m-copy_right { ... }

.g-index .m-dialog {
  display: none;
}

.g-index .m-dialog.z-active {  // 留意此处&的编译结果
  display: block;
}
Copy after login

Isn’t it cool? There is no need to copy and modify a lot of selectors over and over again, and there is no need to sort out the relationships between them. You only need to nest them, and all relationships are as simple and clear as looking at the DOM directly! Free your hands, free your eyes, and increase efficiency at the same time. It is worth noting that when we write Sass, we should try to keep the nesting order of Sass consistent with the DOM. Note that the nesting order is consistent, not the level, because not all elements in the DOM need to be styled.

Let’s mention another scenario to illustrate that the nested writing method of sass is easy to maintain. Assume that there is originally a module m-article_box under g-bd. Now we want to migrate m-article_box from g-bd to g-hd (of course this requirement is somewhat Unreasonable~), let’s look at the original code:

<!-- index.html -->

<!DOCTYPE html>
<html>
<head>
  <title>index</title>
</head>
<body>
  <p class="g-index">
    <p class="g-bd">
      <p class="m-article_box">
        <p class="hd">
          最新文章
        </p>
        <p class="bd">
          <p class="list">
            <p class="item">
              <img class="cover" />
              <p class="info">
                <p class="title">
                  <a href="#">文章标题</a>
                </p>
                <p class="desc">文章简介</p>
              </p>
            </p>
          </p>
        </p>
        <p class="ft">
          <p class="page">
            <a href="#" class="pg">1</a>
            <a href="#" class="pg">2</a>
            <a href="#" class="pg">3</a>
            <a href="#" class="pg">4</a>
          </p>
        </p>
      </p>
    </p>
  </p>
</body>
</html>
Copy after login
.g-bd { ... }
.g-bd .m-article_box { ... }
.g-bd .m-article_box .hd { ... }

.g-bd .m-article_box .bd { ... }
.g-bd .m-article_box .bd .list { ... }
.g-bd .m-article_box .bd .list .item { ... }
.g-bd .m-article_box .bd .list .item .cover { ... }
.g-bd .m-article_box .bd .list .item .info { ... }
.g-bd .m-article_box .bd .list .item .info .title { ... }
.g-bd .m-article_box .bd .list .item .info .desc { ... }

.g-bd .m-article_box .ft { ... }
.g-bd .m-article_box .ft .page { ... }
.g-bd .m-article_box .ft .page .pg { ... }
Copy after login

According to the css method, we need to copy all the parts related to m-article_box from g-bd to g-hd. This is still under the condition that the writing of the module complies with the specifications. If the writing of the module does not comply with the specifications and does not hang all the structures under the m-article_box class, it will really be a disaster~ But now using sass, we only need to Just cut the entire block of m-article_box from g-bd to g-hd (in order to highlight the heavy workload of modification, I specially wrote the entire module structure - not to make up the word count... ):

// 修改前
.g-hd { ... }

.g-bd {
  .m-article_box {
    .hd { ... }
    .bd {
      .list {
        .item {
          .cover {
            ...
          }

          .info {
            .title {
              ...
            }

            .desc {
              ...
            }
          }
        }
      }
    }

    .ft {
      .page {
        .pg { ... }
      }
    }
  }
}

// 修改后
.g-hd {
  .m-article_box {
    .hd { ... }
    .bd {
      .list {
        .item {
          .cover {
            ...
          }

          .info {
            .title {
              ...
            }

            .desc {
              ...
            }
          }
        }
      }
    }

    .ft {
      .page {
        .pg { ... }
      }
    }
  }
}

.g-bd { ... }
Copy after login

Very convenient and less error-prone.

2. Variable

Let’s go directly to the code:

// index.scss

$fontSize: 16px;
$grey: #ccc;

.m-nav {
  font-size: $fontSize;
  color: $grey;
}
Copy after login

Compilation result:

/* index.css */

.m-nav {
  font-size: 16px;
  color: #ccc;
}
Copy after login

Anyone who has written code is familiar with the usage of parameter. It is too simple and straightforward. I don’t want to say too much. Just understand it yourself.

3. Function

// pixels to rems

@function rem($px) {
    @return $px / 640 * 16rem;
}
Copy after login

It's too simple and straightforward. I don't want to say too much. Just understand it yourself.

4、混合(mixin)

混合,顾名思义,就是混合的意思。。。也就是我们可以事先定义一段代码块,在需要使用到的地方,直接引用(include),而在引用之前,这段代码都不会出现在编译文件中,也就是不会生成任何内容。

这也是非常重要的一个特性!我们知道common的体积非常大,而体积大的根本原因是它存放了许许多多的模块。我们设想一下,如果将每一个模块都打包成mixin,那common不就减肥成功了?!多年的顽疾终于看到希望,没有比这更让人惊喜的了!我们这就上车:

/* common.css */

.m-nav { ... }
.m-news { ... }
.m-copy_right { ... }
Copy after login
Copy after login

改造后

// common.scss

@mixin m-nav {
  .m-nav { ... }
}

@mixin m-news {
  .m-news { ... }
}

@mixin m-copy_right {
  .m-copy_right { ... }
}


// index.scss

.g-index {
  @include m-nav;
  @include m-news;
  @include m-copy_right;
}
Copy after login

5、import

这个属性很眼熟?没错,事实上,css本身就有这个属性实现,我们可以在css文件中直接使用import来引入其他文件。那么css的import和sass的import有什么区别?从含义和用法上来说,没有区别,区别在于工作原理。css的import是阻塞的,而sass的import在编译后,其实是合并文件,最后只产出一个css文件,而css则没有合并,该多少个文件就还是多少个文件。

注意:

  1. 只有import一个.sass/.scss文件的时候,才可以省去后缀名,如果是直接import一个.css文件,要补全文件名;


  2. import之后的分号;不要漏写,会报错;


  3. sass如果import的是一个.css文件的话,那它的作用就跟css原生的import作用一样,只有import一个sass文件的时候,才是合并文件。

如下:

// index.scss
@import &#39;common&#39;;
@import &#39;a.css&#39;;
Copy after login

编译结果:

/* index.scss */

.m-nav { ... }
.m-news { ... }
.m-copy_right { ... }

@import url(&#39;a.css&#39;);
Copy after login

css的import之所以没有被普遍使用是有原因的。我们可以大概猜到它的工作原理:a.css import b.css以后,当浏览器加载到页面中的a.css的时候,已经准备按照a.css的内容来渲染页面了,刚解析到第一行,发现a.css居然还import了一个b.css,于是它不得不先放下a.css(既阻塞a.css),去加载b.css,直到b.css加载完,并且优先解析它,然后才开始回来解析a.css——鬼知道b.css会不会又import了c.css……这直接导致了渲染工作滞后,引发性能问题。

说实话我还不如直接用两个link标签去同步加载a.css和b.css,效率会高一些。

所以css的import基本是被抛弃了的属性。

sass的import主要的好处就是把文件合并了,减少了请求。原本需要link好几个css文件的页面,现在只需要一个。

模块化

终于要开始干点正事了,首先我们来回顾一下,上一节我们以规范为基础构建的模块化项目,遗留了一些什么问题。

  1. 冗余 体积庞大的common;


  2. 使用cm-模块区别m-模块,使得后期开发过程中,m-模块向cm-模块转变过程比较繁琐;

……

好像,问题也不是特别多,我们一个一个解决。

为了方便,在这里我们把每个页面所对应的scss文件叫做 页面scss;把变量、函数、混合等(没有被引用或者执行的情况下)编译后不产生实际内容的代码叫做 定义类代码 ,那么相对应的其他内容就是 实际内容代码。

1、mixin.scss

我们知道,一方面,在common中过多地添加模块最终会导致common的体积过大,使得资源冗余,另一方面,为了方便维护,我们又希望尽量多地把模块公有化。

这是一对矛盾,仅靠css本身是无法解决的,但sass可以!如果我们使用mixin来代替直接书写模块,由于mixin并不直接生成代码,而是通过主动引用,才能生成对应内容,那么理论上,common就可以无限多地存放模块而不必占用一点空间!

(注意,这里说的是理论上,实际应用中,文件太过庞大的话,免不了还是要受到命名冲突的限制的,不过这问题不大。)

说干就干,我们把common中的模块全部打包成mixin:

/* common.css */

.m-nav { ... }
.m-news { ... }
.m-copy_right { ... }
Copy after login
Copy after login

改造后

// common.scss

@mixin m-nav {
  .m-nav { ... }
}

@mixin m-news {
  .m-news { ... }
}

@mixin m-copy_right {
  .m-copy_right { ... }
}
Copy after login

调用方式如下:

// index.scss

@import &#39;common&#39;; // 记得先引入common

.g-index {
  @include m-nav;
  @include m-news;
  @include m-copy_right;
}
Copy after login

原本我们会在每个需要用到公共模块的页面中,先引用common,然后再引用页面css,而现在,我们只需要在页面scss中直接@import common;就可以了。

使用common:

<!-- index.html -->

<!DOCTYPE html>
<html>
<head>
  <title>index</title>
  <link rel="stylesheet" type="text/css" href="./style/common.css">
  <link rel="stylesheet" type="text/css" href="./style/index.css">
</head>
<body>
  ...
</body>
</html>
Copy after login

改造后:

// index.scss
@import &#39;common&#39;;
Copy after login
<!-- index.html -->

<!DOCTYPE html>
<html>
<head>
  <title>index</title>
  <link rel="stylesheet" type="text/css" href="./style/index.css">
</head>
<body>
  ...
</body>
</html>
Copy after login
Copy after login

很完美,——至少目前为止是这样。

我们思考一个问题,common除了存放模块之外,还有没有其他内容?答案是肯定的,大家一定知道有个东西叫做css reset(或者normalize.css),这肯定是全局的;另外,如果是做后台管理系统的话,可能还会有bootstrap。当然,还有一些自定义的全局的样式,比如常见的.clearfix,等等。

这些东西目前我们也堆积在common当中,而且合情合理,因为它们都是全局的样式。但是对比起mixin来说,这些实际内容代码显得很少量,有种被淹没的感觉,使得整个common看上去就像只有mixin。但是这些实际内容代码的作用却又非常重要。为了使common的构成更加直观,我们把mixin全部都抽离出来,单独存放一个叫做mixin.scss的文件中,然后在common引用它,这样,mixin的管理更加的规范,而且common的结构也更加清晰了。

抽离mixin还有另外一个重要原因,后面会讲到的,我们希望mixin作为一个纯粹定义类代码文件,随处可以引用而不会生成多余的代码。

原本我们会在每个需要用到公共模块的页面中,先引用common,然后再引用页面css,而现在,我们只需要在页面scss中直接@import mixin;就可以了。

使用mixin:

// index.scss
@import &#39;common&#39;;  // 引入common,如果有需要,common里一样可以引入mixin
@import 'mixin';  // 引入mixin

.g-index {
  ...
}
Copy after login
<!-- index.html -->

<!DOCTYPE html>
<html>
<head>
  <title>index</title>
  <link rel="stylesheet" type="text/css" href="./style/index.css">
</head>
<body>
  ...
</body>
</html>
Copy after login
Copy after login

2、common.scss

好,抽离了mixin之后,我们现在来重新看回common,common里应该是些什么样的内容。上面的内容我们稍稍提到了一点,我们来展开一下。

2.1、css reset(normalize)

我们知道浏览器千差万别,各浏览器的默认样式也是不尽相同,最常见的比如body的默认内边距,p标签的默认内边距,以及ul/ol等等。这些不统一的默认样式经常让我们感到头疼,所以就有人提出一开始写样式就先把它们消除的想法,于是就催生了后来非常流行的reset.css。

起初的reset.css很简单,大概是这样的:

html, body, h1, h2, h3, h4, h5, h6, p, dl, dt, dd, ul, ol, li, p {
  margin: 0;
  padding: 0;
}
Copy after login

没错,就是把几乎所有会用到的标签都给去了内边距和外边距,简单粗暴,这样所有的标签就都统一了,而且在不同的浏览器下也是统一的。

其他的部分每个人有各自的补充,比如有人会把h1~h6的所有字号给定义一遍,以保证在不同浏览器下他们有统一的大小;有人会给a标签设置统一的字体颜色和hover效果,诸如此类等等。

很好,没毛病。我们把这些统称为css reset,然后再统一封装到一个叫做reset.css的文件中,然后每个页面都引用。

这种方式一直以来都挺实用,而且大家也都这么用,没出过什么问题。只是后来有人提出,这种方式太过粗暴(居然还心疼浏览器了)。。。而且会降低页面渲染的性能,最重要的是,这使得我们原本设计出来的表达各种含义的标签儿们,变得毫无特点了。。。

说的好有道理,如果你家里所有人名字不一样但是都长一个样,还有啥意思。

于是,就出现了normalize.css,normalize的目的同样是为了统一各个浏览器下各不相同的默认样式,不过它并不是简单粗暴地全部抹平,而是根据规范,来人为地把那些不符合规范的默认样式“扶正”,从而达到统一各个浏览器默认样式,同时保留各个标签原有特点的目的。

我们不能说reset与normalize这两种思想孰好孰坏,只能说各有各的特点和作用,它们的存在都是为了解决同样的问题。

2.2、插件

一般来说,一个ui插件都会至少包括一个css文件,像bootstrap、datepicker等等。假设我们项目中需要以bootstrap为基础框架,实现快速开发,那么这时候我们就需要在项目中全局引入bootstrap.min.css,当然,还有bootstrap.min.js。说到全局暴露,我们第一时间想到的是common,没错,我们可以在common中引入。

有人问,插件的.css文件怎么import?额,改一下扩展名为.scss就可以了,scss是兼容原生css语法的~

所以最终,我们的common大概是这样子的:

// common.scss

@import &#39;./reset&#39;;
@import &#39;./bootstrap.min&#39;;
@import &#39;./mixin&#39;;
Copy after login
Copy after login

事实上,如果我们不需要使用到 mixin.scss 中的变量和mixin的话,我们可以不引用它。

那么我们的页面scss应该是这样的:

// index.scss

@import &#39;./common&#39;;
@import &#39;./mixin&#39;;

.g-index {
  ...
}
Copy after login

干净,整洁。

3、mixin编写规范

每添加一个新角色,我们就要及时给它设置规范,好让它按照我们的期望工作别添乱。我们接下来来归纳一下mixin的书写规范。

Scenario 1: There are three files in the project: mixin.scss, a.scss (assuming this is a certain function file), and index.scss. A variable $fontSize: 16px; is defined in mixin, and a variable $color is defined in a. : #ccc;, we reference these two files at the same time in the index, then we can directly use the two variables $fontSize and $color in the index - I mean, although we do not see it in the index to the declaration and definition of these two variables, but they just exist.

Is this a good thing or a bad thing? My gut tells me there might be something wrong with this. Yes, is this similar to the pollution we discussed before? It's just that after we referenced common before, index had already occupied many module names before writing anything. Now, because other files are referenced, many variable names of index are occupied. In addition, from a maintenance perspective, this is also problematic. If I don't tell you in advance, or you don't read the mixin and a in advance, do you know where the $color in the index comes from? Suppose we need the font size, do you know which file to modify? In addition, how do you ensure that when you reference mixin and a at the same time, is it possible that there are variables with the same name between them? So who covers whom? These problems may seem small, but when the scale of your project is large, it may be an irreversible disaster (who are you scaring???).

Scenario 2: Suppose our project has a theme color. The border, tab background, navigation bar background, font color, etc. are all this theme color. For the convenience of use, we don’t want to always use the color picker to get the value, so we Define a global variable $color: #ff9900 in the mixin, and then you can happily use it everywhere!

After the entire website was developed, a month later, the designer suddenly came over and said to you: "The boss said that this theme color needs to be changed. It's a bit earthy. Let's change it to bright red." So you looked reluctant but secretly opened it with joy. mixin, change the value of $color to bright red, and then proudly said to the designer: "Fortunately, I have prepared it. It's done. Just take a look." Save it, open the page and see, the designer and your My face turned green, why is the page so ugly? Some words were originally in the theme color, but the background was red, but now after changing it, the whole block became red, and the content was difficult to see clearly. Some borders were originally red, but The font is the original theme color, but now it has been changed, and the border and font have become red.

Designer: "No, no, no, I just want to change the background color."
You: "Didn't you say to change the theme color? That's all."
Designer: "No, just change the background."
You: "No way..."
Designer: "Why not? Can't we just change the background color? Just change it back according to how you set it."
You: "It's not as simple as you think..."

……

Okay, I'm just here to scare you. If you're really good at it, then this won't be a big deal.

So we need to manage (global) variables, just like how we managed mixins. We can't define them wherever we want, nor can we modify a global variable at every turn:

  1. Global variables are only defined in the mixin. Variables defined in other scss files (whether exposed to the global or local) are only regarded as local variables and are not used outside the current file (even if they can be referenced, avoid use);


  2. Where global variables need to be used, directly import mixin;


  3. Generally speaking, you should be careful when defining global variables, and the number of global variables should be as small as possible;


  4. Keep it as unchanged as possible. If the requirements change, unless the purpose is very certain, please add a global variable to gradually replace the parts that need to be modified;


  5. Do not use too general nouns as global variables, such as color. It is recommended to use the description of the color value directly, such as $orange: #ff9900. This makes it easier for us to maintain and expand. If the color value needs to be modified, but it is not All places need to be modified, then we can define a new variable to expand it, such as $red: red.

These points sound a bit erratic. In fact, it is really difficult to explain why we should do this. After all, they are all summaries of experience, so you might as well get familiar with using Sass for a period of time before thinking about these issues in detail.

Note that the above are not rigid rules. At some point, this specification needs to be adjusted based on actual projects, such as the SPA we will talk about later. There is no perfect project, nor is there a development model that can be applied to all projects. Only by adapting measures to local conditions can we better solve problems. Moreover, none of the problems we have mentioned so far are fatal. The fatal problems were avoided when we formulated the specifications in the previous section.

Call module

Question, where to call the module?

Answer, page scss.

在页面scss中调用模块是一个好习惯,它使得我们在每个页面所用到的模块既是一致的又是互相隔离的,不像在common中直接引用模块那样,使得一个页面scss还没有内容的时候就已经被很多模块名污染了。

再提个问题,在页面scss的哪里调用模块?

例一,根类外:

// index.scss

@import &#39;./common&#39;;
@import &#39;./mixin&#39;;

@include m-nav;
@include m-news;
@include m-copy_right;

.g-index {
  ...
}
Copy after login

例二,根类内:

// index.scss

@import &#39;./common&#39;;
@import &#39;./mixin&#39;;

.g-index {
  @include m-nav;
  @include m-news;
  @include m-copy_right;

  ...
}
Copy after login

目前为止,这两种方式都是可以的,至于我为什么用“目前为止”这个词,那是因为我们后面将要讲到的SPA,如果用例一的方式是有问题的。所以我比较鼓励使用例二的方式。当然,我说了,目前为止例一也是没问题的。

性能优化

目前为止,我们的模块化工作已经算是完成了,其实已经可以收工了。不过我们还是可以稍微做一下优化。

1、缓存

我们需要考虑一个问题:缓存。

缓存是我们web开发中最常见的情况之一,很多时候我们都需要跟缓存打交道,特别是在做性能优化的时候。

一般来说,静态资源在被加载到浏览器之后,浏览器会把它本地缓存下来,以便下次请求同个资源的时候可以快速响应,不需要再去远程服务器加载。

我们就css来说,假设我们按照原来的方式,使用多个link去加载reset、bootstrap、common、index这几个文件的话,这几个文件都会被缓存下来,以使得下次再访问这个页面,这个页面的加载速度会快很多。

如果是从index页面跳转到about页面呢?你会发现也很快,因为about页面的全局css(reset、bootstrap、common)和index页面是一样的,而它们在你访问index的时候,已经加载过了,得益于缓存的作用,之后的页面打开都快。

我们现在的方式是,一个页面所用到的所有css文件都被合并成一个,也就不存在相同的文件可以利用缓存这样的优势了。

那我们有办法改进吗?有的!我们只需要把common独立出来,那么common就可以做为被缓存的公共文件了。最终我们从一个页面只引用一个文件变成了一个页面引用两个文件,即common和页面css:

// common.scss

@import &#39;./reset&#39;;
@import &#39;./bootstrap.min&#39;;
@import &#39;./mixin&#39;;
Copy after login
Copy after login
// index.scss

@import &#39;./mixin&#39;;

.g-index {
  ...
}
Copy after login

注意,不同于之前,我们这里的index.scss不再引入common.scss,所以我们最终是得到了两个css文件,而common.css是在所有页面中通过link标签引入的。

如此一来,我们就实现了既能够合并文件,减少请求数,又可以利用缓存,提高加载速度。

2、压缩

代码压缩是优化工作中最基本的一步,css的压缩空间是很大的,尤其是我们这种 垂直的书写方式 ,压缩起来是相当高效的。

在sass中这很简单,sass在编译的时候提供了几种模式,其中的compressed模式是最高效的压缩模式,记得在编译打包的时候选择compressed模式就行了。

总结

总的来说,预处理语言在使我们编程更加美好的同时,也使得规范更加的完善。在css本身无法实现的情况下,我们通过工具来完成了模块化开发。

我不会讲如何去安装和配置sass环境,因为这些w3cplus sass guide有详细的介绍了,建议使用nodejs的方式,不会捣鼓nodejs/npm的前端不是好前端。

最后,我们回到一开始提到的问题——为什么要模块化?现在我们可以先从css的工作来回答,从某种意义上讲,模块化提高了我们编程能力和解决问题的能力,使得构建一个庞大而可扩展可维护的项目成为可能,使得我们能够以架构的思维和眼光去搭建整个项目。

The above is the detailed content of Modular practice of css preprocessing language. For more information, please follow other related articles on the PHP Chinese website!

source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template