Skip to content

根据业务场景动态换肤办法---从入口处考虑 #5

Open
@ghost

Description

根据业务场景动态换肤办法

实现根绝业务场景,比如:不同渠道(pc,微信)等,动态整体换肤功能,主要有两种方法。

使用JS动态设置样式表(客户端换肤)

这种方法主要是在浏览器渲染过程中,根据参数(可能来自请求参数,或者浏览器嗅探)动态设置样式表。

主体代码,大致如下:

var getUrlParam = function(name) {
    var reg = new RegExp("(^|&|#|/?)" + name + "=([^&]*)(&|$)", "i");
    var r = window.location.href.substr(1).match(reg);
    if (r != null) {
        var result = unescape(r[2]);
        if (result.indexOf('#') > 0) {
            result = result.slice(0, result.indexOf('#'));
        };
        return result;
    }
    return null;
};
var type = getUrlParam('type');
function addCssByLink(url){
    var doc=document;
    var link=doc.createElement("link");
    link.setAttribute("rel", "stylesheet");
    link.setAttribute("type", "text/css");
    link.setAttribute("href", url);

    var heads = doc.getElementsByTagName("head");
    if(heads.length)
        heads[0].appendChild(link);
    else
        doc.documentElement.appendChild(link);
}
//方法一:动态生成link标签
//        addCssByLink('css/' + type + '.css');

function setStyleSheet(url) {
    var doc=document;
    var link = doc.getElementById('css');
    if (link){
        link.setAttribute("href", url);
    }else {
        addCssByLink(url);
    }
}
//方法二:动态设置href的值。
setStyleSheet('css/' + type + '.css')

这种方法,确确实实实现了根据不同参数换肤的功能。但是,这种方法会存在闪烁问题。

闪烁问题,主要是因为再次请求、重绘和大量图片等问题造成的。

而且,对于多页应用(非SPA)需要在每一个页面中都执行这段代码,以确保每一个页面都正确显示。

服务端动态设置样式表

这种方式,就是在服务端返回页面时,动态设置html中link标签的属性。

比如,常见的JSP页面可以这样写:

<link type="text/css" rel="stylesheet" href="assets/css/<%=request.getParameter('type')%>.css" />

但是,现在谁还写JSP页面,所以,这种方式忽略。

另外一种方式,就是服务端渲染。这里有几个关键字:单页面应用,服务端渲染,动态生成link,Node中间层。

  • SPA(单页面应用):首先,我们的页面应该设置成单页形式,通过router机制实现页面切换。这种方式有个好处,我们只有一个主页面,所以只需要设置一次link。
  • 服务端渲染:服务端渲染一般都是用来提高首屏加载速度。服务端渲染,就是在服务端返回数据之前,生成一个已经完全装填完毕的页面。在此处,我们正好可以利用这一特点,在返回数据之前,根据参数,动态替换link属性,从而规避在客户端执行JS代码来造成的闪烁问题。
  • 动态生成link:这个在第二点已经介绍了。
  • Node中间层:服务端渲染首先需要服务器具备服务端渲染的能力。在此处,我们可以使用NodeJS。首先,Node对于高并发有天然的优势;其次,独立出一个Node层,有利于前后端分离,让Node来做静态服务管理和首屏渲染等内容。具体架构参考
    http://frontenddev.org/link/full-stack-development-with-nodejs-1.html#heading-2-9http://zhenhua-lee.github.io/react/goku.html

服务端渲染的具体实现方式可以参考如下代码:

'use strict'

var fs = require('fs')
var path = require('path')
var url=require("url");
var querystring=require("querystring");

// Define global Vue for server-side app.js
global.Vue = require('vue')

// Get the HTML layout
var layout = '<!DOCTYPE html>\
    <html>\
    <head>\
    <title>My Vue App</title>\
    <link href="#" rel="stylesheet" type="text/css" />\
    <script src="/assets/vue.js"></script>\
    </head>\
    <body>\
    <div class="app" id="app"></div>\
    <div class="app">test</div>\
    <div class="app">test</div>\
    <div class="app">test</div>\
    <div class="app">test</div>\
    <div class="app">test</div>\
    <div class="app">test</div>\
    <div class="app">test</div>\
    <div class="app">test</div>\
    <div class="app">test</div>\
    <script src="/assets/app.js"></script>\
    <script>app.$mount(\'#app\')</script>\
    </body>\
    </html>'

// Create a renderer
var renderer = require('vue-server-renderer').createRenderer()

// Create an express server
var express = require('express')
var server = express()

// Serve files from the assets directory
server.use('/assets', express.static(
    path.resolve(__dirname, 'assets')
))

// Handle all GET requests
server.get('/assets/index', function (request, response) {
    // Render our Vue app to a string
    renderer.renderToString(
        // Create an app instance
        require('./assets/app')(),
        // Handle the rendered result
        function (error, html) {
            // If an error occurred while rendering...
            if (error) {
                // Log the error in the console
                console.error(error)
                // Tell the client something went wrong
                return response
                        .status(500)
                        .send('Server Error')
            }
            var str=url.parse(request.url,true).query;
            var arg = querystring.parse(url.parse(request.url).query);
            var type = arg.type || 'classic';


            //这里根据参数等信息动态替换link
            var data = layout.replace('<div id="app"></div>', html)
                .replace('<link href="#" rel="stylesheet" type="text/css" />','<link href="/assets/css/'+ type +'.css" rel="stylesheet" type="text/css" />');

            // Send the layout with the rendered app's HTML
            response.send(data)
        }
    )
})

// Listen on port 5000
server.listen(5000, function (error) {
  if (error) throw error
  console.log('Server is running at localhost:5000')
})

关于以上两个方式,我均做了一个简单的Demo,分别可以体验客户端JS替换和服务端替换的差别。具体源代码请联系我。

另外:对于通过PhoneGap或者Cordoba打包的H5应用,虽然,css文件和图片等已经被打包到本地,但是在替换CSS时,仍然会有轻微的闪烁问题。这种方式,是属于客户端替换,本人已亲自体验过这种换肤方式。

对于JS动态替换,折中的实现方式是,另外一套样式尽量不大面积改变DOM结构,对于背景图片和icon等内容,应进行较少量的变化。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions