如何用低代码的思路设计文字描边渐变组件

前言

文字特效设计一直是困扰 Web 前端 Css 世界多年的问题, 比如如何用纯 Css 实现文字描边, 渐变, 阴影等, 由于受限于浏览器兼容性的问题, 我们不得不使用其他替代方案来实现.

平时工作中我们使用 PS 等设计工具能很容易的实现文字渐变等特效, 但是随着可视化 技术的成熟, 我们需要把传统软件的能力移植到 web 端, 让用户在 Web 端也能有和桌面软件一样的体验效果, 那么我们就需要想一套优雅的方案了, 接下来我会从文本特效的

  • 描边
  • 阴影
  • 渐变

这3个纬度来和大家分享一下用前端实现的解决方案, 并提供一种可复用的文本组件设计方案, 让大家更容易的来是使用文字特效.

你将收获

  • 文字渐变, 阴影, 描边的3种方案实现
  • 组件设计的一些通用方法和原则
  • 如果在低代码平台中优雅的设计功能强大的文字组件
  • 技术脑洞

正文

先来介绍一下文字描边和阴影的设计方案.

文字描边的2种css方案

  1. css3text-stroke

我们可以使用如下样式来实现简单的描边效果:

css 复制代码
-webkit-text-stroke: 1px #000000;

在w3c上演示的效果如下:

但是缺点就是兼容性不是特别好, 如果不考虑太多浏览器的兼容问题, 大家可以直接用这种方案.

  1. 使用text-shadow 模仿描边效果

使用此方案有点就是兼容性好, 不需要加浏览器前缀, 方案如下:

css 复制代码
.text-shadow {
  text-shadow: #000 1px 0 0, #000 0 1px 0, #000 -1px 0 0, #000 0 -1px 0;
  color: #fff;
}

显示效果:

刚才介绍的文字描边引用了阴影来实现, 自然我们要想实现文字阴影, 可以直接使用text-shadow, 并且我们可以实现多种阴影的效果, 甚至能用它实现3D文字效果(并不建议这么做, 性能有待考量)

实现文字渐变的3种方案

文字渐变css3也提供了对应的特性, 但是兼容性并不是很好, 目前还不推荐使用, 有如下2种方案:

css 复制代码
// 方案1. 背景裁切+背景渐变实现
{
  background-image: -webkit-gradient(linear, left center, right center, from(blue), to(red)); /* 背景色渐变 */
	-webkit-background-clip: text;  /* 设置背景的显示区域 */
	-webkit-text-fill-color: transparent; /* 避免字体颜色覆盖 */
}

// 方案2: mask
{
  -webkit-mask-image: -webkit-gradient(linear, 0 0, 0 bottom, from(red), to(blue));
}

效果如下:

根据笔者的实验, 更推荐用另一种兼容性更好的方案------ svg.

接下来我们看看使用svg实现文字渐变的效果.

具体实现方案大家需要具备一点的 svg 基础知识, 这里给大家分享一下我的实现代码:

html 复制代码
<svg style={{
    width: '100%',
    textShadow: `${shadow[1]}px ${shadow[0]}px ${shadowSize}px`,
}}>
      <defs>
          <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style={{'stopColor': upColor, 'stopOpacity': 1}} />
            <stop offset="100%" style={{'stopColor': backColor, 'stopOpacity':1}} />
          </linearGradient>
      </defs>
      <text x={pos[1]} y={pos[0]} fill="url(#grad)" style={{fontSize, textAnchor: 'start'}}>Dooring文字渐变效果</text>
</svg>

以上代码中upColor(前景色), backColor(背景色), 我们可以动态配置, 同时我们还可以改变渐变的方向, 通过linearGradient 标签的xy属性. 大家感兴趣的可以私下研究一下.

通过这种方案, 我们就能实现展现能力非常强的文字特效了. 接下来我会分享一下如何用组件化或者低代码的思路, 将文字特效封装成一个高可复用的组件.

如果优雅的设计功能强大的文字组件

在介绍组件设计思路之前,有必要介绍一下著名的SOLID原则.

SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁提出的面向对象编程和面向对象设计的五个基本原则。利用这些原则,程序员能更容易和高效的开发一个可维护和扩展的系统。 SOLID被典型的应用在测试驱动开发上,并且是敏捷开发以及自适应软件开发的基本原则的重要组成部分。

  • S 单一功能原则: 规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的服务都应该严密的和该功能保持一致。

  • O 开闭原则: 规定"软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的",这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。遵循这种原则的代码在扩展时并不需要改变。

  • L 里氏替换原则: 派生类(子类)对象可以在程序中代替其基类(超类)对象,是对子类型的特别定义.

  • I 接口隔离原则: 指明应用或者对象应该不依赖于它不使用的方法。接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样应用或对象只需要知道它们感兴趣的方法。这种缩小的接口也被称为角色接口。接口隔离原则(ISP)的目的是系统去耦合,从而容易重构,更改和重新部署。接口隔离原则是在SOLID (面向对象设计)中五个面向对象设计(OOD)的原则之一,类似于在GRASP (面向对象设计)中的高内聚性。

  • D 依赖反转原则: 是指一种特定的解耦 形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。

掌握好这5个原则将有利于我们开发出更优秀的组件,请默默记住.接下来我们来看看文字特效组件的设计思路.

以上组件设计是结合 组件化 + 低代码 的开发思路设计的, 组件化帮助我们将业务实现封装地更易用, 低代码的思路让我们将组件和业务结合起来, 通过一种更系统的思维来设计组件.

具体在组件场景的用法如下:

html 复制代码
<Text 
  fontSize={14} 
  fontFamily=宋体"
  pos ={[0, 20]}
  animation="fade in"
>
    文字渐变
</Text>

这种方式可以更好的让前端工程师无需关注底层实现直接使用复杂功能, 更近一步地考虑, 如果要让组件通过零代码的方式拖拽配置生成, 我们就需要考虑更多系统性的问题, 比如组件的Schema定义, 系统交互事件如何配置, 如何通过属性配置面板来实现组件样式的搭建.

目前我在Dooring零代码平台中的实现方式是对组件配置项设计一套DSL结构, 通过DSL来动态渲染配置项:

tsx 复制代码
const Chart: ChartSchema = {
  // 配置项列表
  editAttrs: [
    {
      key: 'layerName',
      type: 'Text',
      cate: 'base',
    },
    {
      key: 'y',
      type: 'Number',
      cate: 'base',
    },
    ...DataConfig, // 数据配置项
    ...eventConfig, // 事件配置项
    
  ],
  // 组件默认属性
  config: {
    width: 200,
    height: 200,
    zIndex: 1,
    layerName: '柱状图',
    labelColor: 'rgba(188,200,212,1)',
    // ... 其他配置初始值
    multiColor: ['rgba(91, 143, 249, 1)', 'rgba(91, 143, 249, 1)', 'rgba(91, 143, 249,,1)', 'rgba(91, 143, 249, 1)'],
    data: [
      {
        name: 'A',
        value: 25,
      },
      {
        name: 'B',
        value: 66,
      }
    ],
  },
}

我们通过遍历 editAttrs 结构就可以得到一个属性配置面板:

按照这样的思路, 我们实现一个兼容低代码平台的文字特效组件结构可能长这样:

tsx 复制代码
import React, { memo, useState, useRef, useEffect } from 'react'
import { ITextConfig } from './schema';
import { cpClick, isEditorPage } from '@/utils/cpTool';
import ani from '../animate.css';
import classnames from 'classnames';

const TextCp = memo((props: ITextConfig) => {
  const { 
    cpName,
    text, 
    fontSize,
    fontFamily,
    pos = [0, 20],
    dir,
    bgColor, 
    padding, 
    radius,
    animation,
    animationTurn,
    delay,
    interaction = {}, 
    // ...
  } = props;
  const { type, content } = interaction;
  const [visible, setVisible] = useState(false);
  const [isEditable, setEditable] = useState(false);
  const textRef = useRef<any>(null);
  const [data, setData] = useState<string>('');

  const handleClick = () => {
    cpClick(type, content, () => {
      setVisible(true)
    })
  }

  const handleDoubleClick = () => {
    if(!isEditorPage()) {
      return
    }
    setEditable(true);
    setTimeout(() => {
      textRef.current.focus();
      textRef.current.value = text;
    }, 30)
  }

  const handleSaveText = () => {
    window.handleTextUpdate(textRef.current.value)
    setEditable(false)
  }

  // 数据源...

  const direction = dir === 'hor' ? {x1: "0%", y1: "0%", x2: "100%", y2:"0%"} : {x1: "0%", y1: "0%", x2: "0%", y2:"100%"}

  return (
    <>
      <div 
        className={classnames('dooring-text', animation !== 'none' && ani[`animate__${animation}`])}
        style={{ 
          fontSize: _gaw(fontSize), 
          fontFamily,
          fontWeight: +fontWeight,
          backgroundColor: bgColor,
          padding,
          borderRadius: radius,
          animationIterationCount: animationTurn,
          animationDelay: delay + 's',
          letterSpacing: space + 'px',
        }} 
          onClick={handleClick} 
          onDoubleClick={handleDoubleClick}
        >
          <svg style={{
              width: '100%',
              textShadow: `${shadow[1]}px ${shadow[0]}px ${shadowSize}px`,
            }}>
            <defs>
                <linearGradient id="grad" {...direction}>
                  <stop offset="0%" style={{'stopColor': upColor, 'stopOpacity': 1}} />
                  <stop offset="100%" style={{'stopColor': backColor, 'stopOpacity':1}} />
                </linearGradient>
            </defs>
            <text x={pos[1]} y={pos[0]} fill="url(#grad)" style={{fontSize, textAnchor: 'start'}}>{ addSpaceEntity(data || text) }</text>
          </svg>
        { isEditable && <input ref={textRef} style={{
          border: 'none',
          position: 'absolute',
          top: 0,
          left: 0,
          fontSize,
          fontFamily,
          width: '100%',
          height: '100%',
          textAlign: 'left',
          color: 'rgba(60,60,60,1)'
        }} onBlur={handleSaveText} />}
      </div>
    </>
  );
});
export default TextCp;

这样我们既可以单独使用组件, 也可以在Dooring零代码平台中通过拖拽的方式更简单地使用组件了.

最后

我们可以通过上面介绍的方式将任何已有组件包装的更扩展, 组件的开发结构变成了:

  • schema(DSL定义)
  • index(组件实现)

这样设计之后组件会有更好的可移植性和扩展性, 当然未来webcompoent更加成熟稳定了, 我们可以通过更健壮的模式来设计组件. 后续我也会持续分享可视化低代码相关的技术实现, 欢迎大家随时和我交流.

更多推荐

相关推荐
学习路上_write28 分钟前
FPGA/Verilog,Quartus环境下if-else语句和case语句RT视图对比/学习记录
单片机·嵌入式硬件·qt·学习·fpga开发·github·硬件工程
谎言西西里29 分钟前
Git入门指南:掌握基本概念与日常操作
git·github
喵叔哟29 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web