Liaoct / blog

在这里记录一些个人经验与思考
22 stars 2 forks source link

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

Open ghost opened 7 years ago

ghost commented 7 years ago

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

实现根绝业务场景,比如:不同渠道(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中间层。

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

'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等内容,应进行较少量的变化。