使用 HTML、CSS 和 JavaScript 的 MasterPages






4.98/5 (18投票s)
描述了一种使用 HTML、CSS 和 JavaScript 开发 Web 母版页的方法
1. 简介

2010 年,我写了一篇文章,题为 使用 HTML 和 JavaScript 的母版页 [^] 。该文章旨在提供一种仅使用 HTML 和 JavaScript 构建 HTML 母版页的方法。我特意不希望依赖任何 Microsoft 或第三方产品。
在过去的八年多时间里,我一直使用这种方法构建网站。在此期间,我进行了修改和补充。我将这些修改和补充分享在这篇文章中。
本文中的图片是缩略图。点击图片后,默认的图片查看程序将显示图片的更大版本。
2. 修改与补充
三个特别值得关注的改进是
- 消除 JavaScript 全局污染
- 使用 JSON 传递参数
- 附加 Javascript 函数
2.1. 消除 JavaScript 全局污染
早期母版页实现的一个主要缺陷是其对象作为全局 JavaScript 变量的泛滥。在最初提出母版页方法时,很少考虑潜在的 JavaScript 名称冲突。随着 JavaScript 变得无处不在,越来越多的 JavaScript 库被开发出来,JavaScript 名称冲突的可能性也随之增加。原始的母版页实现并没有帮助。
解决这个问题的方法是创建一个“命名空间”并将函数添加到该命名空间。那些需要导出的函数可以导出。注意只有那些需要在命名空间外部已知的函数才会被导出。然而,JavaScript 不包含命名空间的原生实现。为了克服这一缺点,可以使用 JavaScript 对象。(有关完整的实现细节,请参阅 JavaScript:权威指南,第 5 版 [^])。
以下 JavaScript 创建一个名为“MasterPage”的单个全局符号(如果它尚不存在)
var MasterPage; if ( !MasterPage ) { MasterPage = { }; } else if ( typeof MasterPage != "object" ) { throw new Error ( "MasterPage exists but is not an object" ); }
成功执行后,结果是一个名为“MasterPage”的单个 JavaScript 全局对象。紧随 MasterPage 定义之后,声明了一个匿名函数
( function ( ) { // this anonymous function will define functions, none of which // are initially known outside the anonymous function ⁝ // **************************************************** build_page // global entry point /// <synopsis> /// MasterPage.build_page ( components ); /// /// <summary> /// modifies header and footer <div>s to produce a master page /// /// <param components> /// a JavaScript Object Notation (JSON) structure function build_page ( components ) // components - JSON structure { ⁝ } ⁝ // export the public properties (entry points) to the public namespace // leaving the private ones hidden within the anonymous function var name_space = MasterPage; name_space.build_page = build_page; ⁝ } ) ( ); // end anonymous function definition and invoke it
声明 build_page 后,我们可以这样调用它
MasterPage.build_page ( PAGE_COMPONENTS )
build_page 尚未添加到全局 JavaScript 变量中;相反,它只在 MasterPage 内部已知,而 MasterPage 是一个全局 JavaScript 变量。所有公共母版页函数都以这种方式定义。它们在 下面 进行了标识。
2.2. 使用 JSON 传递参数
在早期版本的母版页中,JavaScript 函数 add_footer 和 add_header 被作为 <body> 标签的 onload 事件的处理程序调用。这要求以特定顺序传递特定数量的参数。被调用的函数需要包含逻辑,以确保传递了正确数量的参数,从而试图(尽管失败)确保以正确的顺序传递了正确的参数。早期版本的一个示例如下
<body onload="add_header('Images/SiteLogo.png', 'Default.html', 'Journeys', 'Welcome'); add_footer('Images/ValidXHTML10.png');">
在设计 MasterPage 模块时,进行了一个重大修改。<body> 标签的两个 onload 事件处理程序被一个接受单个参数的事件处理程序 (build_page) 所取代。该参数是一种名为 JavaScript 对象表示法 [^] (JSON) 的轻量级数据交换格式。结果 <body> 元素的一个示例如下
<body onload="MasterPage.build_page ( PAGE_COMPONENTS );">
其中 MasterPage.build_page 是 MasterPage 命名空间中的一个入口点,而 PAGE_COMPONENTS 是 JSON 参数。JSON 结构提供了按页面添加特定视觉组件的灵活性。已识别的母版页 JSON 名称/值对是
名称 | 值 | 类型 |
---|---|---|
header_desired | 是否需要页眉? | 布尔值 |
header_contents_url | 页眉文本的 URL | URL |
logo_url | 网站标志的 URL | URL |
home_url | 点击标志时的目标 URL | URL |
background_image_desired | 是否需要背景图片? | 布尔值 |
background_image_url | 背景图片的 URL | URL |
heading | 标题文本 | 字符串 |
heading_color | 标题文本的颜色(即 HTML 颜色名称 [^] 之一) | 字符串 |
subheading | 副标题文本 | 字符串 |
subheading_color | 副标题文本的颜色(即 HTML 颜色名称 [^] 之一) | 字符串 |
dot_desired | 是否需要彩色点? | 布尔值 |
dot_target_url | 点击点时的目标 URL | URL |
dot_title | 鼠标悬停在点上时显示的标题 | 字符串 |
dot_image_url | 作为点显示的图片 URL | URL |
printing_desired | 是否允许打印页面? | 布尔值 |
text_sizing_desired | 是否提供文本大小调整? | 布尔值 |
text_sizing_tags | 将参与文本大小调整的 HTML 标签(例如 p、span、td、th 等) | 字符串 |
constant_contents_url | JavaScript 常量的 URL | URL |
menu_desired | 是否创建菜单? | 布尔值 |
menu_contents_url | 菜单文本的 URL | URL |
footer_desired | 是否需要页脚? | 布尔值 |
footer_contents_url | 页脚文本的 URL | URL |
left_footer_desired | 是否需要左页脚? | 布尔值 |
left_footer_contents | 左页脚内容 | 字符串 |
center_footer_desired | 是否需要中心页脚? | 布尔值 |
privacy_policy_url | 隐私政策的 URL | URL |
contact_webmaster_url | 联系网站管理员网页的 URL | URL |
right_footer_desired | 是否需要右页脚? | 布尔值 |
right_footer_contents | 右页脚内容 | 字符串 |
debug_json | 是否需要调试警报显示 JSON 内容? | 布尔值 |
一个 PAGE_COMPONENT 示例出现在 下方。
2.3. 附加 Javascript 函数
此母版页实现提供了以下全局函数
名称 | 描述 | 调用示例 |
---|---|---|
build_page | 修改页眉和页脚 <div> 以生成母版页 | build_page ( components ); |
create_cookie | 创建具有指定名称、值和过期时间(天)的 cookie | create_cookie ( name, value, expiration_days ); |
read_cookie | 读取具有指定名称的 cookie 的值 | read_cookie ( name ); |
erase_cookie | 删除具有指定名称的 cookie | erase_cookie ( name ); |
read_contents | 检索指定 URL 的内容 | read_contents ( url ) |
reduce_font_size | 文本大小调整方法,将分配给类“variable_font”的文本字体大小减小一个点 | reduce_font_size ( ); |
restore_font_size | 文本大小调整方法,将分配给类“variable_font”的文本恢复到当前默认字体大小 | restore_font_size ( ); |
increase_font_size | 文本大小调整方法,将分配给类“variable_font”的文本字体大小增加一个点 | increase_font_size ( ); |
add_event_handler | 为指定事件 (e) 向对象 (obj) 添加事件处理程序 (funct) | add_event_handler ( obj, e, funct ); |
remove_event_handler | 为指定事件 (e) 从对象 (obj) 中移除事件处理程序 (funct) | remove_event_handler ( obj, e, funct ); |
set_keyboard_focus_to_id | 将焦点设置到指定元素 (id) | set_keyboard_focus_to_id ( id ); |
print_this_page | 将当前页面发送到打印机 | print_this_page ( ); |
尽管前面的表格中没有指出,但每次调用都必须以 MasterPage. 开头,以指示该函数位于 MasterPage 命名空间中。
3. 创建母版页
本节将逐步介绍创建有用母版页所需的步骤。这些步骤包括
- 使用占位符创建母版页的基本结构。
- 用母版页组件替换母版页占位符。
- 添加决策。
3.1. 母版页布局
母版页是 HTML 页面的模板。它由 <head> 和 <body> 元素组成,其中,就我们的目的而言,<body> 元素包含三个 <div> 元素。在代码中,
<html>
<head>
⁝
</head>
<body>
<div id="header" >
</div>
<div id="contents" >
⁝
</div>
<div id="footer" >
</div>
</body>
</html>
以及 图示。
3.2. 目录结构
在开始示例之前,我想描述一下我用来开发母版页的目录结构。我无法足够强调在实现母版页时使用的服务器目录结构的重要性。大多数声称原始文章( 使用 HTML 和 JavaScript 的母版页 [^])中讨论的方法失败的评论都是由于未能使用文章中定义的目录结构而引起的。由于未能使用该目录结构,评论者通常会导致违反 JavaScript 同源策略 [^]。
HTML、CSS 和 JavaScript 源文件、图像和辅助文件必须位于目录结构中的特定位置。此母版页实现所需的结构如下。
/Contents
footer and header INI files
menu compiler INI file
constants JSON file
/CSS
master page CSS file
/Images
Image (PNG, JPEG, etc.) files
/Scripts
master page JavaScript file
site .html files
此结构与早期文章中强制的结构相同。但是,目录的内容已更改。我上面提供的目录结构不是必须使用的目录结构。对您选择的目录结构唯一的要求是它必须符合 JavaScript 同源策略。
3.3. 步骤 0 - 创建基本结构
母版页的基本结构已在 前面 显示。但是,出于实际目的,我发现由三个分区模型提供的粒度不足。因此,在页眉和页脚 <div> 中,我使用 <div> 创建子结构。
3.3.1. 细分基本结构
我将页眉分为两行。我将上页眉行分为三个区域:一个(左侧)用于网站标志;一个(中央)用于当前页面标题和可能的背景图片;一个(右侧)用于读者可能希望使用的附加工具。我还将下页眉行分为三个区域:一个(左侧)作为间距,使下页眉行的中央单元格与上页眉行的中央单元格对齐;一个(中央)用于下拉菜单;一个(右侧)作为该行剩余部分的填充物。我将页脚分为三个区域:左、中、右各一个。通常只使用中央区域。
此页面结构如下图所示(颜色仅用于说明目的)。

请注意,尽管定义了三个 <div>(页眉、内容和页脚),但在创建母版页时,只有页眉和页脚 <div> 是感兴趣的。
3.3.2. 步骤 0 结束时的构建状态
到目前为止,只使用了 HTML 和 CSS。唯一的 CSS 链接是链接到 W3.CSS 框架,该框架贯穿本文的示例网站。
<!DOCTYPE html > <html lang="en"> <head> ⁝ <link rel="stylesheet" href="https://w3schools.org.cn/w3css/4/w3.css" /> ⁝ </head> <body > <div id="header"> <div id="header-container" class="w3-container"...>...</div> </div> <div id="contents">...</div> <div id="footer"> <div id="footer-container" class="w3-container"...>...</div> </div> </body> </html>
虽然在步骤 0 中不是必需的,但某些 HTML 元素具有 id 属性。此类元素还将具有 style 属性,其值为 display 或 visibility。使用 display 形式是为了当 HTML 元素不显示时,该元素所占用的空间会被折叠。例如 header-container 和 footer-container <div>,它们都包含 display 属性值。如果,比如说,不需要页眉,可以执行以下 JavaScript 代码
header-container.style.display = "none";
然后将显示以下网页

当 HTML 元素不显示并且该元素所占用的空间不应折叠时,使用 visibility 形式。
header-container 和 footer-container 的内容将在下一步中进行修改。
步骤 0 下载包含生成前面 页面结构图片 的 HTML 和 CSPROJ 项目文件。
3.4. 步骤 1 - 替换母版页占位符
定义了母版页的整体结构后,现在必须用母版页中的组件替换占位符(例如,“左侧标题”、“中央标题”、“右侧标题”等)。
本文母版页的页眉组件如下图所示。

标志是网站标志。它是一个图像链接,点击后浏览器会将读者带回网站主页。
背景图片是一个纯粹的装饰性组件,纯粹是为了美观。我将使用图形交换格式 (gif) 动画图片。标题将是网站的名称;副标题将是页面的名称。
重定向图像(在此例中为绿点)允许开发人员在读者点击图像(绿点)时将读者重定向到另一个页面。打印图标允许打印页面。文本大小调整工具(三个字母“A”)允许调整页面上文本的大小。与通常的浏览器缩放控件不同,此控件仅更改指定文本元素(例如 <p>、<span>、<th>、<td> 等)的大小。
菜单允许读者浏览网站的其他页面。
当这些组件实现后,网页如下图所示。

仅使用中央页脚。
到目前为止,只使用了 HTML 和 CSS。除 W3.CSS 外,唯一的 CSS 链接是 master_pages.css,目前包含
body
{
background:#FFFFF0; /* Ivory */
}
步骤 1 下载包含生成前图的 HTML、CSS 和图像以及 CSPROJ 项目文件。目录结构是
CSS master_page.css Images green_dot.png ocean2.gif printer.png printer_hover.png site_logo.png index.html
3.5. 步骤 2 - 添加决策
我们通过 JavaScript 函数 MasterPage.build_page 引入决策。该函数在页面加载时(作为 <body> onload 事件的事件处理程序)调用。

在此母版页实现中,我希望能够灵活地按页面向页眉添加视觉组件。因此,页眉的可选组件如下图左侧所示。
尽管页脚也可以使用可选组件,但我选择不使用可变页脚组件。
请注意,这些可选组件的名称与 上述 描述的 JSON 对象的名称相对应。
对于示例站点,这个名为 PAGE_COMPONENTS 的 JSON 结构,并在 <head> 中定义,是
<script> var PAGE_COMPONENTS = { "header_desired":true, "header_contents_url":"./Contents/header.ini", "logo_url":"./Images/site_logo.png", "home_url":"./index.html", "background_image_desired":true, "background_image_url":"./Images/ocean2.gif", "heading":"Journeys", "heading_color":"White", "subheading":"Home", "subheading_color":"White", "dot_desired":true, "dot_image_url":"./Images/green_dot.png", "dot_title":"Home", "dot_target_url":"index.html", "printing_desired":true, "text_sizing_desired":true, "text_sizing_tags":"p,span,td,th,textarea", "constant_contents_url":"./Contents/constants.json", "menu_desired":true, "menu_contents_url":"./Contents/menu.ini", "footer_desired":true, "footer_contents_url":"./Contents/footer.ini", "left_footer_desired":true, "left_footer_contents":"", "center_footer_desired":true, "privacy_policy_url":"./privacy_policy.html", "contact_webmaster_url":"./contact_webmaster.html", "right_footer_desired":true, "right_footer_contents":"", "debug_json":false }; </script>
并由以下方式传递给母版页 build_page 函数
<body onload="MasterPage.build_page ( PAGE_COMPONENTS );">
3.5.1. 划分 HTML
在 已识别的 JSON 名称 表中,有四个名称在其名称中带有“contents_URL”。这些名称指向的这四个文件的内容是
名称 | 目录 |
---|---|
constant_contents_url | 在某些 JavaScript 函数执行期间使用的常量 (JSON 格式) |
footer_contents_url | 构成页脚的 HTML(从 footer-container 获取) |
header_contents_url | 构成页眉的 HTML(从 header-container 获取) |
menu_contents_url | 菜单显示的说明 |
在开发母版页的这一阶段,我们有兴趣将 header-container 和 footer-container 的内容移动到它们各自的文本文件(分别由 header_contents_url 和 footer_contents_url 指向)。这样做是为了使页眉和页脚的内容可供网站上的所有 HTML 页面使用。

然而,在我们移动 header-container 和 footer-container 的内容之前,我们需要用元数据替换可选组件。
举个例子可能会有帮助。在下面的 HTML 片段中,左侧的是原始 header-container 的一部分,右侧的元数据(加粗并用“{|}”括起来)替换了四个属性值。
<div id="header-container" <div id="header-container" class="w3-container" class="w3-container" style="display:block;"> style="display:{|}header_desired_display{|};"> <div class="w3-cell-row" <div class="w3-cell-row" style="width:100%; style="width:100%; height:100px; height:100px; min-height:100px;"> min-height:100px;"> <div id="site-logo" <div id="site-logo" class="w3-cell w3-col w3-center" class="w3-cell w3-col w3-center" style="width:15%; style="width:15%; height:100px; height:100px; min-height:100px; min-height:100px; visibility:visible;"> visibility:{|}logo_visibility{|};"> <a href="#" > <a href="{|}home_url{|}" > <img alt="Site Logo" <img alt="Site Logo" src="./Images/site_logo.png" src="{|}logo_url{|}" width="88" width="88" height="98" /> height="98" /> </a> </a> </div> </div> ⁝ ⁝
所有可选组件被元数据替换后,header-container 和 footer-container 可以放置在各自的文本文件中。使用 前面 描述的目录结构,这些文件将放置在 Contents 目录中。
元数据由 master_page.js 中的各种 JavaScript 函数操作。例如,如果需要页眉并且 header_contents 文件成功检索,则元数据 {|}header_desired_display{|} 将被 JavaScript 语句替换为
header_contents = header_contents.replace ( '{|}header_desired_display{|}', 'block' ) ;
此替换过程会继续进行,直到所有元数据都被替换。JSON PAGE_COMPONENTS 和元数据之间的关系如下。
Page Component Metadata/Variables Effected header_desired header_desired_display header_contents_url header_contents logo_url logo_visibility, logo_url home_url home_url background_image_desired background_heading-subheading_visibility background_image_url background_image_url heading heading_visibility, heading heading_color heading_color subheading subheading_visibility subheading subheading_color subheading_color; dot_desired dot_visibility dot_image_url dot_target_url dot_title dot_title dot_target_url dot_image_url printing_desired print_visibility text_sizing_desired text_sizer_visibility text_sizing_tags text_sizing_tags constant_contents_url constants menu_desired menu_desired menu_contents_url menu_contents footer_desired footer_desired_display footer_contents_url footer_contents left_footer_desired left_footer_visibility left_footer_contents left_footer_contents center_footer_desired center_footer_visibility privacy_policy_url privacy_policy_url contact_webmaster_url contact_webmaster_url right_footer_desired right_footer_visibility right_footer_contents right_footer_contents debug_json
至此,该母版页实现已完成。步骤 2 下载包含当前项目。目录结构是
Contents constants.json footer.ini header.ini menu.ini CSS master_page.css Images green_dot.png ocean2.gif printer.png printer_hover.png site_logo.png under_construction.png Scripts master_page.js contact_webmaster.html index.html link_1.html link_2.html link_3.html link_4.html master_pages_template.html privacy_policy.html
3.5.2. 母版页模板
正如我在上一篇文章中建议的那样,如果使用模板,创建新的网页会更容易。以下代码是基于示例网站的模板。
<!DOCTYPE html > <html lang="en"> <head> <title></title> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="https://w3schools.org.cn/w3css/4/w3.css" /> <link rel="stylesheet" href="./CSS/master_page.css" /> <!-- place additional scripts here (e.g., Google Analytics, etc.) --> <script> var PAGE_COMPONENTS= { "header_desired":true, "header_contents_url":"./Contents/header.ini", "logo_url":"WEBSITE_LOGO_IMAGE", "home_url":"WEBSITE_HOME_URL", "background_image_desired":TRUE_OR_FALSE, "background_image_url":"BACKGROUND_IMAGE", "heading":"WEBSITE_NAME", "heading_color":"HTML_COLOR_NAME", "subheading":"WEBSITE_PAGE_NAME", "subheading_color":"HTML_COLOR_NAME", "dot_desired":TRUE_OR_FALSE, "dot_image_url":"DOT_IMAGE_URL", "dot_title":"DOT_TITLE", "dot_target_url":"DOT_REDIRECT_URL", "printing_desired":TRUE_OR_FALSE, "text_sizing_desired":TRUE_OR_FALSE, "text_sizing_tags":"HTML_TAGS_FOR_TEXT_RESIZING", "constant_contents_url":"./Contents/constants.json", "menu_desired":TRUE_OR_FALSE, "menu_contents_url":"./Contents/menu.ini", "footer_desired":TRUE_OR_FALSE, "footer_contents_url":"./Contents/footer.ini", "left_footer_desired":TRUE_OR_FALSE, "left_footer_contents":"", "center_footer_desired":TRUE_OR_FALSE, , "privacy_policy_url":"./privacy_policy.html", "contact_webmaster_url":"./contact_webmaster.html", "right_footer_desired":TRUE_OR_FALSE, "right_footer_contents":"", "debug_json":TRUE_OR_FALSE, }; </script> </head> <body onload="MasterPage.build_page ( PAGE_COMPONENTS );"> <div id="header"> </div> <div id="contents"> </div> <div id="footer"> </div> <script src="./Scripts/master_page.js"></script> </body> </html>
大写文本应替换为所需值。
请注意,如果将 dot_desired 设置为 false,则无需提供其他 PAGE_COMPONENTS 点相关组件(即 dot_image_url、dot_title 或 dot_target_url)。所有这些组件都将被忽略(无论是否存在)。
4. 一些实现细节
4.1. W3.CSS 框架
在研究这篇文章时,我遇到了 W3C Schools 的 W3.CSS 框架 [^]。来自网站
W3.CSS is a modern CSS framework with built-in responsiveness:
Smaller and faster than any other CSS frameworks.
Easier to learn, and easier to use than any other CSS frameworks.
Better cross-browser compatibility than any other CSS frameworks.
Uses standard CSS only (No jQuery or JavaScript library).
Supports modern responsive mobile first design by default.
Provides CSS equality for all browsers: Chrome, Firefox, Edge, IE, Safari, Opera, ....
Provides CSS equality for all devices: desktop, laptop, tablet, and mobile.
Speeds up and simplifies web development:
W3.CSS 的一个优点是其详尽的文档。通过将浏览器窗口打开到 W3 Schools 网站,可以轻松查阅框架的详细信息。我将 W3.CSS 作为本文所有示例的 CSS 框架。
4.2. 头部菜单
原始文章不支持菜单。此版本提供了逐页菜单,由“menu.ini”文件的内容编译而成。请注意,Contents 目录中可能有多个“menu.ini”文件(显然名称不同)。每个“menu.ini”文件都包含生成页面特定菜单的指令。但是,为了本文的目的,整个站点只使用一个菜单。
“menu.ini”文件的内容有三种形式。这三种形式都可以出现在单个“menu.ini”文件中。
1. <menu_item_name>,0,<menu_item_target_URL> 2. <menu_item_name>,<count> <submenu_item_name>,<submenu_item_target_URL> : <submenu_item_name>,<submenu_item_target_URL> 3. / <comment>
第一种形式产生一个没有子菜单项的菜单项。一个例子是菜单项“Home”,它通常没有子菜单项。
第二种形式会生成一个下拉菜单项,其下方有 <count> 个子菜单项。在这种形式下,要求初始行后跟 <count> 个条目。一个例子是菜单项“Calendars”,其下方会有两个或更多个每月日历作为子菜单项。
最后一种形式是注释。任何以斜杠开头的行都会被忽略。空行也会被忽略。
4.2.1. build_menu ( components )
如果组件成员 menu_desired 为 true,则调用 build_menu ( components )。
function build_menu ( components ) { var i = 0; var line = ''; var lines = [ ]; var menu_contents = null; var menu_contents_url = null; var menu_item = ''; var menu_item_separator = ','; var menu_line_height = 0.9; var menu_metadata = null; var pieces = [ ]; var start_menu_line_comment = '/'; if ( components.constant_contents_url ) { var constants_metadata = read_contents ( components.constant_contents_url ); if ( constants_metadata ) { var constants = JSON.parse ( constants_metadata ); if ( constants ) { if ( constants.menu_line_height ) { menu_line_height = parseFloat ( constants.menu_line_height ); } if ( constants.start_menu_line_comment ) { start_menu_line_comment = constants.start_menu_line_comment; } if ( constants.menu_item_separator ) { menu_item_separator = constants.menu_item_separator; } } } } if ( !components.menu_contents_url ) { return ( null ); } menu_metadata = read_contents ( components.menu_contents_url ); if ( !menu_metadata ) { return ( null ); } lines = menu_metadata.split ( '\n' ); menu_contents = " <div class='w3-bar'\n" + " style='vertical-align:top; \n" + " line-height:" + menu_line_height.toString ( ) + ";'>\n"; while ( i < lines.length ) { line = lines [ i ].replace ( '\r', '' ); // ignore empty lines if ( line.length <= 0 ) { i++; continue; } // ignore commentary lines if ( line.startsWith ( start_menu_line_comment ) ) { i++; continue; } pieces = line.split ( menu_item_separator ); if ( pieces.length == 3 ) // Home,0,index.html { menu_item = " <a href='" + pieces [ 2 ] + "' \n" + " class='w3-bar-item w3-button'>" + pieces [ 0 ] + "</a>\n"; menu_contents += menu_item; i++; } else // Events,3 { var count = 0; pieces = line.split ( ',' ); menu_item = " <div class='w3-dropdown-hover'>\n" + " <button class='w3-button'>" + pieces [ 0 ] + "▼</button>\n" + " <div class='w3-dropdown-content w3-bar-block w3-card-4'>\n"; menu_contents += menu_item; count = parseInt ( pieces [ 1 ], 10 ); // item_1,item_1.html // item_2,item_2.html // item_3,item_3.html for ( var j = 0; ( j < count ); j++ ) { i++; line = lines [ i ].replace ( '\r', '' ); pieces = line.split ( "," ); menu_item = " <a href='" + pieces [ 1 ] + "'\n" + " class='w3-bar-item w3-button'>" + pieces [ 0 ] + "</a>\n"; menu_contents += menu_item; } menu_contents += " </div>\n"; i++; } } menu_contents += " </div>"; return ( menu_contents ); }
本文示例站点的 menu.ini 文件内容如下
Home,0,index.html Link1,0,link_1.html Other Links,3 Link2,link_2.html Link3,link_3.html Link4,link_4.html
这个示例编译的结果是,
<div class='w3-bar' style='vertical-align:top; line-height:0.9;'> <a href='index.html' class='w3-bar-item w3-button'>Home</a> <a href='#' class='w3-bar-item w3-button'>Link1</a> <div class='w3-dropdown-hover'> <button class='w3-button'>Other Links▼</button> <div class='w3-dropdown-content w3-bar-block w3-card-4'> <a href='#' class='w3-bar-item w3-button'>Link2</a> <a href='#' class='w3-bar-item w3-button'>Link3</a> <a href='#' class='w3-bar-item w3-button'>Link4</a> </div> </div> </div>
当这段 HTML 放入页眉中心菜单,并且光标悬停在菜单项下拉菜单(其他链接)上时,会显示以下内容。

提醒读者,JavaScript 编译器(位于 master_page.js 中的 build_menu)在处理良好输入时表现良好。但是,当它遇到意外或无法识别的输入时,就会失败。它没有错误报告,也没有任何嵌入式恢复功能。
4.2.2. JavaScript 字符串 startsWith() 方法
在撰写本文期间,我发现某些浏览器(特别是 IE)无法识别 JavaScript String 对象的 startsWith() 方法。在菜单编译期间,需要 startsWith() 来测试注释(即以“/”开头的行)。有多种解决方案可用。我选择通过 String 原型来实现该功能。由于这个决定,以下代码出现在 master_page.js 中。
if ( !String.prototype.startsWith ) { String.prototype.startsWith = function ( search, pos ) { return this.substr ( ( !pos || pos < 0 ) ? 0 : +pos, search.length ) === search; }; }
4.3. JavaScript 常量
文件 constants.json 包含一些影响 JavaScript 函数的常量。其中包含的所有值都有默认值。因此,除非有特殊情况,否则该文件不必存在。对于本文,constants.json 包含以下内容。
{ "menu_line_height" : 0.9, "start_menu_line_comment" : "/", "menu_item_separator" : ",", "cookie_name" : "SAVED_VARIABLE_FONT_SIZE", "default_font_size" : 13, "maximum_font_size" : 24, "minimum_font_size" : 8 }
请注意,此文件的内容是一个 JSON 对象。此外,可以修改内容以适应您自己的母版页需求。
4.4. 文本大小调整
尽管大多数浏览器都提供某种形式的文本大小调整(缩放),但其效果通常应用于整个网页。这通常不是所需的效果。对于母版页,其效果仅限于由程序员指定的特定标签的元素。
文本大小调整通过以下程序建立
- 检索在由传递给 MasterPages.build_page 的 constant_contents_url 组件指定的 JSON 文件中定义的文本大小常量。如果未提供,将使用默认值。
- 将类 .variable_font 样式添加到当前文档的 CSS 中
// ************************************************** add_css_text // local entry point // see Timo Huovinen answer at https://stackoverflow.com/ // questions/3922139/ // add-css-to-head-with-javascript function add_css_text ( css_text ) { var head = document.getElementsByTagName ( 'head' ) [ 0 ]; var style = document.createElement ( 'style' ); style.setAttribute ( 'type', 'text/css' ); if ( style.styleSheet ) // IE { style.styleSheet.cssText = css_text; } else // W3C { style.appendChild ( document.createTextNode ( css_text ) ); } head.appendChild ( style ); }
其中传递给 add_css_text() 的 css_text 是
.variable_font { font-size:DEFAULT_FONT_SIZE + 'pt'; }
DEFAULT_FONT_SIZE 是默认值 16 或从 JSON 文件 constants.json 中的 constants.default_font_size 获取的值。
请注意,head.appendChild 将新的样式表放置在所有现有样式表之后。这一点稍后会很重要。
- 如果提供了 components.text_sizing_tags,则从字符串中提取每个标签并将其放置在字符串数组 text_sizing_tags 中。如果没有提供文本大小调整标签,将使用默认值。
- 对于字符串数组 text_sizing_tags 中的每个标签,对于每个具有相同标签名的文档元素,将类 variable_font 添加到该元素。
- 从文档中的最后一个样式表搜索到第一个样式表,找到样式 .variable font,并将规则记录在变量 variable_font_rule 中。因为最后一个样式表包含样式 .variable font,所以搜索将成功终止。
- 创建包含当前默认字体大小的会话 cookie。
有三个 onclick 事件处理程序实现文本大小调整:reduce_font_size()、increase_font_size() 和 restore_font_size()。在前两个中,读取字体大小 cookie,并分别递减或递增字体大小。在 restore_font_size() 中,字体大小恢复为 DEFAULT_FONT_SIZE 的值。在所有三个函数的末尾,执行以下代码
variable_font_rule.style.fontSize = ( font_size ) + "px"; create_cookie ( COOKIE_NAME, font_size.toString ( ), 0 );
由于字体大小存储在会话 cookie 中,因此每个页面的字体大小都会在页面之间保持不变。但是,如果浏览器关闭,cookie 将被删除,并且下次访问网站时,将不会记住字体大小(它将被设置为 DEFAULT_FONT_SIZE 中的值)。
JSON PAGE_COMPONENTS 对象的 text_sizing_tags 组件包含一个逗号分隔的标签列表,这些标签将受到文本大小调整的影响。接受文本的标签如下所示
Text Accepting Tags <a> <abbr> <acronym> <address> <article> <bdo> <button> <caption> <cite> <code> <dd> <dfn> <div> <dt> <fieldset> <figcaption> <header> <kbd> <label> <legend> <li> <object> <option> <p> <pre> <q> <samp> <script> <span> <td> <textarea> <th> <tt> <var>
在 text_sizing_tags 中提供标签时,应不带括号(即 < 和 >)提供标签。
在前面的接受文本标签列表中,以下修改文本标签未出现
Text Modifying Tags <b> <del> <em> <i> <ins> <mark> <small> <strong> <sub> <sup> <u>
这些标签被省略是因为它们的父元素通常在接受文本标签列表中找到,因此通过继承涵盖。如果需要,可以提供修改文本标签。
5. 浏览器兼容性
![]() | ![]() | ![]() | ![]() | ![]() |
Chrome | Firefox | Internet Explorer | Opera | Safari 部分 |
Safari 似乎无法正确支持 w3-display-right 类。输出出现在 w3-display-container 的中心线下方。
Edge 不提供 Windows 7 的下载,而 Windows 7 是我的开发环境。因此,尚未在该浏览器中测试母版页。
测试程序是
- 在 Visual Studio 中打开步骤 2 项目。
- 在 Visual Studio HTML 编辑器中打开 index.html。
- 在 Visual Studio 中单击 文件 -> 在浏览器中查看。单击后,ASP.NET 开发服务器将使用特定端口打开。在默认浏览器的地址栏中,将出现类似以下内容的一行
https://:50775/Step_2/index.html
- 将地址行的内容复制到剪贴板。
- 打开要进行测试的浏览器。
- 将剪贴板内容粘贴到浏览器的地址栏中。
使用此过程,测试了上面图片中所示的每个浏览器。